Skip to content

Commit feb7208

Browse files
authored
Merge pull request DeusData#1104 from LA-10/fix/769-file-qn-keeps-extension
feat(store): index-format rebuild boundary for the File-QN change
2 parents 095acc5 + 4d9da40 commit feb7208

9 files changed

Lines changed: 405 additions & 1 deletion

File tree

‎Makefile.cbm‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -560,7 +560,7 @@ TEST_DISCOVER_SRCS = \
560560

561561
TEST_GRAPH_BUFFER_SRCS = tests/test_graph_buffer.c
562562

563-
TEST_PIPELINE_SRCS = tests/test_registry.c tests/test_pipeline.c tests/test_cross_repo.c tests/test_fqn.c tests/test_route_canon.c tests/test_path_alias.c tests/test_configlink.c tests/test_infrascan.c tests/test_worker_pool.c tests/test_parallel.c tests/test_index_resilience.c tests/test_call_reference_contract.c tests/repro/repro_call_scope_usages.c tests/repro/repro_call_argument_usages.c tests/repro/repro_reference_precision.c tests/repro/repro_lexical_binding_precision.c tests/repro/repro_call_argument_matrix_a.c tests/repro/repro_call_argument_matrix_b.c tests/repro/repro_call_node_behaviors.c tests/repro/repro_language_registry.c tests/repro/repro_call_node_manifest.c tests/repro/repro_lsp_ordered_signatures.c tests/repro/repro_lsp_ordered_local.c tests/repro/repro_ts_overload_return_chains.c tests/repro/repro_harness_cleanup.c tests/repro/repro_runner_filter.c
563+
TEST_PIPELINE_SRCS = tests/test_registry.c tests/test_pipeline.c tests/test_cross_repo.c tests/test_fqn.c tests/test_route_canon.c tests/test_path_alias.c tests/test_configlink.c tests/test_infrascan.c tests/test_worker_pool.c tests/test_parallel.c tests/test_index_resilience.c tests/test_index_format.c tests/test_call_reference_contract.c tests/repro/repro_call_scope_usages.c tests/repro/repro_call_argument_usages.c tests/repro/repro_reference_precision.c tests/repro/repro_lexical_binding_precision.c tests/repro/repro_call_argument_matrix_a.c tests/repro/repro_call_argument_matrix_b.c tests/repro/repro_call_node_behaviors.c tests/repro/repro_language_registry.c tests/repro/repro_call_node_manifest.c tests/repro/repro_lsp_ordered_signatures.c tests/repro/repro_lsp_ordered_local.c tests/repro/repro_ts_overload_return_chains.c tests/repro/repro_harness_cleanup.c tests/repro/repro_runner_filter.c
564564

565565
TEST_WATCHER_SRCS = tests/test_watcher.c
566566

‎src/mcp/mcp.c‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8343,6 +8343,9 @@ static char *handle_index_repository(cbm_mcp_server_t *srv, const char *args) {
83438343
srv, doc, root, project_name, repo_path, persistence, p, excluded_dirs, excluded_count,
83448344
file_errors, file_error_count, has_logfile ? logfile_path : NULL);
83458345
yyjson_mut_obj_add_str(doc, root, "status", degraded ? "degraded" : "indexed");
8346+
if (cbm_pipeline_had_format_migration(p)) {
8347+
yyjson_mut_obj_add_bool(doc, root, "format_migration", true);
8348+
}
83468349
} else {
83478350
yyjson_mut_obj_add_str(doc, root, "status", "error");
83488351
yyjson_mut_obj_add_str(doc, root, "hint",

‎src/pipeline/pipeline.c‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,10 @@ struct cbm_pipeline {
191191
int committed_nodes;
192192
int committed_edges;
193193

194+
/* #769: set when a stale-format index was routed through the one-time
195+
* full rebuild, so the MCP response can surface the migration. */
196+
bool format_migration;
197+
194198
/* ADR (project_summaries) captured before a full-reindex DB delete, so it
195199
* can be restored after the rebuild. NULL when no ADR existed. Issue #516. */
196200
char *saved_adr;
@@ -225,6 +229,10 @@ void cbm_pipeline_set_pkgmap(CBMHashTable *map) {
225229
g_pkgmap = map;
226230
}
227231

232+
bool cbm_pipeline_had_format_migration(const cbm_pipeline_t *p) {
233+
return p && p->format_migration;
234+
}
235+
228236
/* ── Timing helper ──────────────────────────────────────────────── */
229237

230238
static double elapsed_ms(struct timespec start) {
@@ -1399,6 +1407,24 @@ static int try_incremental_or_delete_db(cbm_pipeline_t *p, cbm_file_info_t *file
13991407
free(db_path);
14001408
return CBM_PIPELINE_FORCE_FULL_REINDEX;
14011409
}
1410+
1411+
cbm_store_t *fmt_store = cbm_store_open_path_query(db_path);
1412+
int fmt = 0;
1413+
if (fmt_store) {
1414+
cbm_store_get_format_version(fmt_store, &fmt);
1415+
cbm_store_close(fmt_store);
1416+
}
1417+
if (fmt != CBM_INDEX_FORMAT_VERSION) {
1418+
cbm_log_info("pipeline.route", "path", "format_change_reindex", "stored_format",
1419+
itoa_buf(fmt));
1420+
p->format_migration = true;
1421+
int adr_rc = capture_existing_adr(p, db_path);
1422+
(void)cbm_unlink(db_path);
1423+
(void)cbm_remove_db_sidecars(db_path);
1424+
free(db_path);
1425+
return adr_rc != 0 ? adr_rc : CBM_PIPELINE_FORCE_FULL_REINDEX;
1426+
}
1427+
14021428
cbm_log_info("pipeline.route", "path", "incremental_manifest");
14031429
int rc = cbm_pipeline_run_incremental(p, db_path, files, file_count, baseline_manifest,
14041430
baseline_count, force_full_on_mismatch);
@@ -1753,6 +1779,11 @@ int cbm_pipeline_publish_staged(char *stage_path, const cbm_pipeline_generation_
17531779
ok = cbm_store_adr_store(store, generation->project, generation->adr_content) ==
17541780
CBM_STORE_OK;
17551781
}
1782+
1783+
if (ok) {
1784+
ok = cbm_store_set_format_version(store, CBM_INDEX_FORMAT_VERSION) == CBM_STORE_OK;
1785+
}
1786+
17561787
cbm_log_info("publish.timing", "block", "writes", "elapsed_ms",
17571788
itoa_buf((int)elapsed_ms(t_pub)));
17581789
cbm_clock_gettime(CLOCK_MONOTONIC, &t_pub);

‎src/pipeline/pipeline.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ int cbm_pipeline_get_mode(const cbm_pipeline_t *p);
9393
* to NULL/0 when p is NULL or nothing was excluded. Do not free. */
9494
void cbm_pipeline_get_excluded(const cbm_pipeline_t *p, char ***out, int *count);
9595

96+
bool cbm_pipeline_had_format_migration(const cbm_pipeline_t *p);
97+
9698
/* Committed node/edge counts captured at dump time (-1 when dump did not run).
9799
* Nodes are the #334 plausibility-gate axis; edges are informational only. */
98100
void cbm_pipeline_get_committed_counts(const cbm_pipeline_t *p, int *nodes, int *edges);

‎src/store/store.c‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3515,6 +3515,36 @@ int cbm_store_list_files(cbm_store_t *s, const char *project, char ***out, int *
35153515
return CBM_STORE_OK;
35163516
}
35173517

3518+
/* ── Index format version (PRAGMA user_version) ─────────────────── */
3519+
3520+
int cbm_store_get_format_version(cbm_store_t *s, int *out) {
3521+
if (!s || !s->db || !out) {
3522+
return CBM_STORE_ERR;
3523+
}
3524+
*out = 0;
3525+
sqlite3_stmt *stmt = NULL;
3526+
if (sqlite3_prepare_v2(s->db, "PRAGMA user_version;", CBM_NOT_FOUND, &stmt, NULL) !=
3527+
SQLITE_OK) {
3528+
return CBM_STORE_ERR;
3529+
}
3530+
if (sqlite3_step(stmt) == SQLITE_ROW) {
3531+
*out = sqlite3_column_int(stmt, 0);
3532+
}
3533+
sqlite3_finalize(stmt);
3534+
return CBM_STORE_OK;
3535+
}
3536+
3537+
/* PRAGMA values cannot be bound; format the integer constant in. Writes, so
3538+
* only call on a read-write connection (never from configure_pragmas). */
3539+
int cbm_store_set_format_version(cbm_store_t *s, int version) {
3540+
if (!s || !s->db) {
3541+
return CBM_STORE_ERR;
3542+
}
3543+
char sql[ST_BUF_64];
3544+
snprintf(sql, sizeof(sql), "PRAGMA user_version = %d;", version);
3545+
return exec_sql(s, sql);
3546+
}
3547+
35183548
/* ── Node neighbor names ──────────────────────────────────────── */
35193549

35203550
static int query_neighbor_names(sqlite3 *db, const char *sql, int64_t node_id, int limit,

‎src/store/store.h‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ typedef struct cbm_store cbm_store_t;
2323
#define CBM_STORE_OK 0
2424
#define CBM_STORE_ERR (-1)
2525
#define CBM_STORE_NOT_FOUND (-2)
26+
#define CBM_INDEX_FORMAT_VERSION 1
2627

2728
/* ── Data structures ────────────────────────────────────────────── */
2829

@@ -92,6 +93,13 @@ void cbm_store_node_degree(cbm_store_t *s, int64_t node_id, int *in_deg, int *ou
9293
* Returns CBM_STORE_OK or CBM_STORE_ERR. */
9394
int cbm_store_list_files(cbm_store_t *s, const char *project, char ***out, int *count);
9495

96+
/* Persisted index-format identity. Bump when a change alters the QN scheme
97+
* or node identity of an already-written graph, so an old DB is routed
98+
* through the full-reindex path instead of producing a mixed graph.
99+
* 1 = File QNs keep the file extension (#769). */
100+
int cbm_store_get_format_version(cbm_store_t *s, int *out);
101+
int cbm_store_set_format_version(cbm_store_t *s, int version);
102+
95103
/* Get caller/callee names for a node (CALLS/HTTP_CALLS/ASYNC_CALLS edges).
96104
* Returns 0 on success. Caller must free each out_callers[i]/out_callees[i]
97105
* and the arrays themselves. */

‎tests/test_fqn.c‎

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,77 @@ TEST(fqn_compute_basic_rs) {
8989
PASS();
9090
}
9191

92+
TEST(fqn_compute_file_sibling_distinct) {
93+
char *ts = cbm_pipeline_fqn_compute("proj", "app/badge/badge.component.ts", "__file__");
94+
char *html = cbm_pipeline_fqn_compute("proj", "app/badge/badge.component.html", "__file__");
95+
char *scss = cbm_pipeline_fqn_compute("proj", "app/badge/badge.component.scss", "__file__");
96+
ASSERT_NOT_NULL(ts);
97+
ASSERT_NOT_NULL(html);
98+
ASSERT_NOT_NULL(scss);
99+
ASSERT_STR_NEQ(ts, html);
100+
ASSERT_STR_NEQ(ts, scss);
101+
ASSERT_STR_NEQ(html, scss);
102+
free(ts);
103+
free(html);
104+
free(scss);
105+
PASS();
106+
}
107+
108+
TEST(fqn_compute_symbol_still_strips) {
109+
ASSERT_FQN(cbm_pipeline_fqn_compute("proj", "app/badge/badge.component.ts", "BadgeComponent"),
110+
"proj.app.badge.badge.component.BadgeComponent");
111+
PASS();
112+
}
113+
114+
TEST(fqn_module_siblings_still_share) {
115+
ASSERT_FQN(cbm_pipeline_fqn_module("proj", "app/badge/badge.component.ts"),
116+
"proj.app.badge.badge.component");
117+
ASSERT_FQN(cbm_pipeline_fqn_module("proj", "app/badge/badge.component.html"),
118+
"proj.app.badge.badge.component");
119+
PASS();
120+
}
121+
122+
/* #769: exact __file__ QNs for component siblings. */
123+
TEST(fqn_compute_file_sibling_exact) {
124+
ASSERT_FQN(cbm_pipeline_fqn_compute("proj", "app/badge/badge.component.ts", "__file__"),
125+
"proj.app.badge.badge.component.ts.__file__");
126+
ASSERT_FQN(cbm_pipeline_fqn_compute("proj", "app/badge/badge.component.html", "__file__"),
127+
"proj.app.badge.badge.component.html.__file__");
128+
ASSERT_FQN(cbm_pipeline_fqn_compute("proj", "app/badge/badge.component.scss", "__file__"),
129+
"proj.app.badge.badge.component.scss.__file__");
130+
PASS();
131+
}
132+
133+
TEST(fqn_compute_file_multi_extension) {
134+
ASSERT_FQN(cbm_pipeline_fqn_compute("proj", "app/badge/badge.spec.ts", "__file__"),
135+
"proj.app.badge.badge.spec.ts.__file__");
136+
ASSERT_FQN(cbm_pipeline_fqn_compute("proj", "archive.tar.gz", "__file__"),
137+
"proj.archive.tar.gz.__file__");
138+
PASS();
139+
}
140+
141+
TEST(fqn_compute_file_no_extension) {
142+
ASSERT_FQN(cbm_pipeline_fqn_compute("proj", "Makefile", "__file__"),
143+
"proj.Makefile.__file__");
144+
ASSERT_FQN(cbm_pipeline_fqn_compute("proj", "LICENSE", "__file__"),
145+
"proj.LICENSE.__file__");
146+
PASS();
147+
}
148+
149+
TEST(fqn_compute_file_dotfile) {
150+
ASSERT_FQN(cbm_pipeline_fqn_compute("proj", ".gitignore", "__file__"),
151+
"proj..gitignore.__file__");
152+
ASSERT_FQN(cbm_pipeline_fqn_compute("proj", "config/.env", "__file__"),
153+
"proj.config..env.__file__");
154+
PASS();
155+
}
156+
157+
TEST(fqn_compute_file_index_ts) {
158+
ASSERT_FQN(cbm_pipeline_fqn_compute("proj", "app/index.ts", "__file__"),
159+
"proj.app.index.ts.__file__");
160+
PASS();
161+
}
162+
92163
/* ── Nested paths ─────────────────────────────────────────────── */
93164

94165
TEST(fqn_compute_nested_two_levels) {
@@ -595,6 +666,14 @@ SUITE(fqn) {
595666
RUN_TEST(fqn_file_qn_distinguishes_same_stem_header_source_issue964);
596667
RUN_TEST(fqn_module_qn_still_strips_extension);
597668
RUN_TEST(fqn_compute_basic_rs);
669+
RUN_TEST(fqn_compute_file_sibling_distinct);
670+
RUN_TEST(fqn_compute_symbol_still_strips);
671+
RUN_TEST(fqn_module_siblings_still_share);
672+
RUN_TEST(fqn_compute_file_sibling_exact);
673+
RUN_TEST(fqn_compute_file_multi_extension);
674+
RUN_TEST(fqn_compute_file_no_extension);
675+
RUN_TEST(fqn_compute_file_dotfile);
676+
RUN_TEST(fqn_compute_file_index_ts);
598677

599678
/* fqn_compute: nested paths */
600679
RUN_TEST(fqn_compute_nested_two_levels);

0 commit comments

Comments
 (0)