Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion TOOL_CONTRACT.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ Risk levels: `low` → `SAFE TO PROCEED`, `medium` → `REVIEW CALLERS FIRST`, `
- Lists `scores[]` from `ix subsystems --list` as subsystem health
- Takes the file count from `stats.nodes.byKind` (kind `file`) and totals from `nodes.total` / `edges.total`
- Flags multi-file subsystems with low cohesion (< 0.4) with ⚠. Ix's `coupling` is an edge-weight count, not a 0–1 ratio, so it is shown but not judged
- When either `ix subsystems` call reports the architecture map stale (`map_stale: true`, Ix#880), adds one line under the heading: `_Map is stale (regions as of map rev 7, 42 files); recent edits may not be reflected — a background refresh is running._` (the parenthetical only when Ix gives `regions_as_of`). Without the field (fresh map, older CLI) the output is unchanged
- When either `ix subsystems` call reports the architecture map stale (`map_stale: true`, Ix#880), adds one line under the heading: `` _Map is stale (regions as of map rev 7, 42 files); recent edits may not be reflected — run `ix map --remap` to refresh it now._ `` (the parenthetical only when Ix gives `regions_as_of`). Without the field (fresh map, older CLI) the output is unchanged

**Example output:**
```
Expand Down
10 changes: 8 additions & 2 deletions runtime/map-stale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,16 @@ export function splitLlmMapStale(text: string): { stale: MapStale | null; body:
return { stale, body: kept.join("\n").trim() };
}

/** The one line a tool puts under its heading while the map is stale. */
/**
* The one line a tool puts under its heading while the map is stale. It does
* not say a background refresh is under way: Ix also reports a stale map when
* background refresh is off (IX_MAP_BACKGROUND_REFRESH=0), failed or could
* not start. It carries the remedy Ix's own llm record gives, which the tool
* drops from the body.
*/
export function mapStaleLine(stale: MapStale): string {
const asOf = stale.regionsAsOf
? ` (regions as of map rev ${stale.regionsAsOf.map_rev}, ${stale.regionsAsOf.file_count} file${stale.regionsAsOf.file_count === 1 ? "" : "s"})`
: "";
return `_Map is stale${asOf}; recent edits may not be reflected — a background refresh is running._`;
return `_Map is stale${asOf}; recent edits may not be reflected — run \`ix map --remap\` to refresh it now._`;
}
34 changes: 32 additions & 2 deletions tests/map-stale.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,24 @@ describe("splitLlmMapStale", () => {
expect(body).toBe("map files=1");
});

test("two records: both come out, the first one names the map", () => {
const second = 'diagnostic code=map_stale message="Stale map (map rev 9, 50 files)."';
const { stale, body } = splitLlmMapStale(`${RECORD}\nmap files=42 regions=3\n${second}\nregion id=a label=X`);
expect(stale).toEqual({ regionsAsOf: { map_rev: 7, file_count: 42 } });
expect(body).toBe("map files=42 regions=3\nregion id=a label=X");
});

test("a record that is not the first line is still found and taken out", () => {
const { stale, body } = splitLlmMapStale(`map files=42 regions=3\n${RECORD}\nregion id=a label=X`);
expect(stale).toEqual({ regionsAsOf: { map_rev: 7, file_count: 42 } });
expect(body).toBe("map files=42 regions=3\nregion id=a label=X");
});

test("a 1-file map is read from the singular \"1 file\"", () => {
const { stale } = splitLlmMapStale('diagnostic code=map_stale message="Stale map (map rev 3, 1 file)."\nmap files=1');
expect(stale).toEqual({ regionsAsOf: { map_rev: 3, file_count: 1 } });
});

test("text without the record comes back unchanged", () => {
const text = "map files=1 regions=1\ndiagnostic code=other message=x";
expect(splitLlmMapStale(text)).toEqual({ stale: null, body: text });
Expand All @@ -61,10 +79,22 @@ describe("splitLlmMapStale", () => {
describe("mapStaleLine", () => {
test("names the map when known", () => {
expect(mapStaleLine({ regionsAsOf: { map_rev: 7, file_count: 42 } })).toBe(
"_Map is stale (regions as of map rev 7, 42 files); recent edits may not be reflected — a background refresh is running._",
"_Map is stale (regions as of map rev 7, 42 files); recent edits may not be reflected — run `ix map --remap` to refresh it now._",
);
expect(mapStaleLine({})).toBe(
"_Map is stale; recent edits may not be reflected — a background refresh is running._",
"_Map is stale; recent edits may not be reflected — run `ix map --remap` to refresh it now._",
);
});

test("a 1-file map says \"1 file\"", () => {
expect(mapStaleLine({ regionsAsOf: { map_rev: 3, file_count: 1 } })).toBe(
"_Map is stale (regions as of map rev 3, 1 file); recent edits may not be reflected — run `ix map --remap` to refresh it now._",
);
});

test("does not promise a background refresh", () => {
for (const s of [{}, { regionsAsOf: { map_rev: 7, file_count: 42 } }]) {
expect(mapStaleLine(s)).not.toContain("background refresh");
}
});
});
4 changes: 2 additions & 2 deletions tests/success-fixtures.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -593,8 +593,8 @@ describe("ix-smells on real output", () => {
* added, as Ix's `mapStaleJson` / `mapStaleLlm` add them.
*/
describe("a stale architecture map is said once, under the heading", () => {
const STALE = "_Map is stale (regions as of map rev 1, 38 files); recent edits may not be reflected — a background refresh is running._";
const STALE_NO_ASOF = "_Map is stale; recent edits may not be reflected — a background refresh is running._";
const STALE = "_Map is stale (regions as of map rev 1, 38 files); recent edits may not be reflected — run `ix map --remap` to refresh it now._";
const STALE_NO_ASOF = "_Map is stale; recent edits may not be reflected — run `ix map --remap` to refresh it now._";
const withoutLine = (output: string, line: string) => output.replace(`${line}\n\n`, "");

for (const llm of [false, true]) {
Expand Down
Loading