fix: align INTEL_FILES constant with actual agent output filenames (#2225)
The gsd-intel-updater agent writes file-roles.json, api-map.json, dependency-graph.json, arch-decisions.json, and stack.json. But INTEL_FILES in intel.cjs declared files.json, apis.json, deps.json, arch.md, and stack.json. Only stack.json matched. Every query/status/ diff/validate call iterated INTEL_FILES and found nothing, reporting all intel files as missing even after a successful refresh. Update INTEL_FILES to use the agent's actual filenames. Remove the arch.md special-case code paths (mtime-based staleness, text search, .md skip in validate) since arch-decisions.json is JSON like the rest. Update all intel tests to use the new canonical filenames. Closes #2205 Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -19,10 +19,10 @@ const crypto = require('crypto');
|
||||
const INTEL_DIR = '.planning/intel';
|
||||
|
||||
const INTEL_FILES = {
|
||||
files: 'files.json',
|
||||
apis: 'apis.json',
|
||||
deps: 'deps.json',
|
||||
arch: 'arch.md',
|
||||
files: 'file-roles.json',
|
||||
apis: 'api-map.json',
|
||||
deps: 'dependency-graph.json',
|
||||
arch: 'arch-decisions.json',
|
||||
stack: 'stack.json'
|
||||
};
|
||||
|
||||
@@ -204,10 +204,8 @@ function intelQuery(term, planningDir) {
|
||||
const matches = [];
|
||||
let total = 0;
|
||||
|
||||
// Search JSON intel files
|
||||
// Search all JSON intel files
|
||||
for (const [_key, filename] of Object.entries(INTEL_FILES)) {
|
||||
if (filename.endsWith('.md')) continue; // Skip arch.md here
|
||||
|
||||
const filePath = intelFilePath(planningDir, filename);
|
||||
const data = safeReadJson(filePath);
|
||||
if (!data) continue;
|
||||
@@ -219,14 +217,6 @@ function intelQuery(term, planningDir) {
|
||||
}
|
||||
}
|
||||
|
||||
// Search arch.md
|
||||
const archPath = intelFilePath(planningDir, INTEL_FILES.arch);
|
||||
const archMatches = searchArchMd(archPath, term);
|
||||
if (archMatches.length > 0) {
|
||||
matches.push({ source: INTEL_FILES.arch, entries: archMatches });
|
||||
total += archMatches.length;
|
||||
}
|
||||
|
||||
return { matches, term, total };
|
||||
}
|
||||
|
||||
@@ -257,20 +247,10 @@ function intelStatus(planningDir) {
|
||||
|
||||
let updatedAt = null;
|
||||
|
||||
if (filename.endsWith('.md')) {
|
||||
// For arch.md, use file mtime
|
||||
try {
|
||||
const stat = fs.statSync(filePath);
|
||||
updatedAt = stat.mtime.toISOString();
|
||||
} catch (_e) {
|
||||
// intentionally silent: fall through on error
|
||||
}
|
||||
} else {
|
||||
// For JSON files, read _meta.updated_at
|
||||
const data = safeReadJson(filePath);
|
||||
if (data && data._meta && data._meta.updated_at) {
|
||||
updatedAt = data._meta.updated_at;
|
||||
}
|
||||
// All intel files are JSON — read _meta.updated_at
|
||||
const data = safeReadJson(filePath);
|
||||
if (data && data._meta && data._meta.updated_at) {
|
||||
updatedAt = data._meta.updated_at;
|
||||
}
|
||||
|
||||
let stale = true;
|
||||
@@ -409,8 +389,7 @@ function intelValidate(planningDir) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip non-JSON files (arch.md)
|
||||
if (filename.endsWith('.md')) continue;
|
||||
// All intel files are JSON — validate _meta and entries structure
|
||||
|
||||
// Parse JSON
|
||||
let data;
|
||||
|
||||
@@ -160,7 +160,7 @@ describe('intelQuery', () => {
|
||||
});
|
||||
|
||||
test('finds matches in JSON file keys', () => {
|
||||
writeIntelJson(planningDir, 'files.json', {
|
||||
writeIntelJson(planningDir, 'file-roles.json', {
|
||||
_meta: { updated_at: new Date().toISOString() },
|
||||
entries: {
|
||||
'src/auth/controller.ts': { size: 1024, type: 'typescript' },
|
||||
@@ -170,12 +170,12 @@ describe('intelQuery', () => {
|
||||
|
||||
const result = intelQuery('auth', planningDir);
|
||||
assert.strictEqual(result.total, 1);
|
||||
assert.strictEqual(result.matches[0].source, 'files.json');
|
||||
assert.strictEqual(result.matches[0].source, 'file-roles.json');
|
||||
assert.strictEqual(result.matches[0].entries[0].key, 'src/auth/controller.ts');
|
||||
});
|
||||
|
||||
test('finds matches in JSON file values', () => {
|
||||
writeIntelJson(planningDir, 'deps.json', {
|
||||
writeIntelJson(planningDir, 'dependency-graph.json', {
|
||||
_meta: { updated_at: new Date().toISOString() },
|
||||
entries: {
|
||||
express: { version: '4.18.0', type: 'runtime', used_by: ['src/server.ts'] },
|
||||
@@ -188,7 +188,7 @@ describe('intelQuery', () => {
|
||||
});
|
||||
|
||||
test('search is case-insensitive', () => {
|
||||
writeIntelJson(planningDir, 'files.json', {
|
||||
writeIntelJson(planningDir, 'file-roles.json', {
|
||||
entries: {
|
||||
'src/AuthController.ts': { type: 'typescript' },
|
||||
},
|
||||
@@ -198,24 +198,25 @@ describe('intelQuery', () => {
|
||||
assert.strictEqual(result.total, 1);
|
||||
});
|
||||
|
||||
test('finds matches in arch.md text', () => {
|
||||
writeIntelMd(planningDir, 'arch.md', [
|
||||
'# Architecture',
|
||||
'',
|
||||
'The system uses a layered architecture with REST API endpoints.',
|
||||
'Authentication is handled by JWT tokens.',
|
||||
].join('\n'));
|
||||
test('finds matches in arch-decisions.json entries', () => {
|
||||
writeIntelJson(planningDir, 'arch-decisions.json', {
|
||||
_meta: { updated_at: new Date().toISOString() },
|
||||
entries: {
|
||||
'jwt-auth': { decision: 'Use JWT tokens for stateless authentication', status: 'accepted' },
|
||||
'rest-api': { decision: 'REST API endpoints for all services', status: 'accepted' },
|
||||
},
|
||||
});
|
||||
|
||||
const result = intelQuery('JWT', planningDir);
|
||||
assert.strictEqual(result.total, 1);
|
||||
assert.strictEqual(result.matches[0].source, 'arch.md');
|
||||
assert.strictEqual(result.matches[0].source, 'arch-decisions.json');
|
||||
});
|
||||
|
||||
test('searches across multiple intel files', () => {
|
||||
writeIntelJson(planningDir, 'files.json', {
|
||||
writeIntelJson(planningDir, 'file-roles.json', {
|
||||
entries: { 'src/auth.ts': { exports: ['authenticate'] } },
|
||||
});
|
||||
writeIntelJson(planningDir, 'apis.json', {
|
||||
writeIntelJson(planningDir, 'api-map.json', {
|
||||
entries: { '/api/auth': { method: 'POST', handler: 'authenticate' } },
|
||||
});
|
||||
|
||||
@@ -244,30 +245,30 @@ describe('intelStatus', () => {
|
||||
test('reports missing files as stale', () => {
|
||||
const result = intelStatus(planningDir);
|
||||
assert.strictEqual(result.overall_stale, true);
|
||||
assert.strictEqual(result.files['files.json'].exists, false);
|
||||
assert.strictEqual(result.files['files.json'].stale, true);
|
||||
assert.strictEqual(result.files['file-roles.json'].exists, false);
|
||||
assert.strictEqual(result.files['file-roles.json'].stale, true);
|
||||
});
|
||||
|
||||
test('reports fresh files as not stale', () => {
|
||||
writeIntelJson(planningDir, 'files.json', {
|
||||
writeIntelJson(planningDir, 'file-roles.json', {
|
||||
_meta: { updated_at: new Date().toISOString() },
|
||||
entries: {},
|
||||
});
|
||||
|
||||
const result = intelStatus(planningDir);
|
||||
assert.strictEqual(result.files['files.json'].exists, true);
|
||||
assert.strictEqual(result.files['files.json'].stale, false);
|
||||
assert.strictEqual(result.files['file-roles.json'].exists, true);
|
||||
assert.strictEqual(result.files['file-roles.json'].stale, false);
|
||||
});
|
||||
|
||||
test('reports old files as stale', () => {
|
||||
const oldDate = new Date(Date.now() - 25 * 60 * 60 * 1000).toISOString();
|
||||
writeIntelJson(planningDir, 'files.json', {
|
||||
writeIntelJson(planningDir, 'file-roles.json', {
|
||||
_meta: { updated_at: oldDate },
|
||||
entries: {},
|
||||
});
|
||||
|
||||
const result = intelStatus(planningDir);
|
||||
assert.strictEqual(result.files['files.json'].stale, true);
|
||||
assert.strictEqual(result.files['file-roles.json'].stale, true);
|
||||
assert.strictEqual(result.overall_stale, true);
|
||||
});
|
||||
});
|
||||
@@ -303,24 +304,24 @@ describe('intelDiff', () => {
|
||||
);
|
||||
|
||||
// Add a file after snapshot
|
||||
writeIntelJson(planningDir, 'files.json', { entries: {} });
|
||||
writeIntelJson(planningDir, 'file-roles.json', { entries: {} });
|
||||
|
||||
const result = intelDiff(planningDir);
|
||||
assert.ok(result.added.includes('files.json'));
|
||||
assert.ok(result.added.includes('file-roles.json'));
|
||||
});
|
||||
|
||||
test('detects changed files since snapshot', () => {
|
||||
// Write initial file
|
||||
writeIntelJson(planningDir, 'files.json', { entries: { a: 1 } });
|
||||
writeIntelJson(planningDir, 'file-roles.json', { entries: { a: 1 } });
|
||||
|
||||
// Take snapshot
|
||||
intelSnapshot(planningDir);
|
||||
|
||||
// Modify file
|
||||
writeIntelJson(planningDir, 'files.json', { entries: { a: 1, b: 2 } });
|
||||
writeIntelJson(planningDir, 'file-roles.json', { entries: { a: 1, b: 2 } });
|
||||
|
||||
const result = intelDiff(planningDir);
|
||||
assert.ok(result.changed.includes('files.json'));
|
||||
assert.ok(result.changed.includes('file-roles.json'));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -341,7 +342,7 @@ describe('intelSnapshot', () => {
|
||||
});
|
||||
|
||||
test('saves snapshot with file hashes', () => {
|
||||
writeIntelJson(planningDir, 'files.json', { entries: {} });
|
||||
writeIntelJson(planningDir, 'file-roles.json', { entries: {} });
|
||||
|
||||
const result = intelSnapshot(planningDir);
|
||||
assert.strictEqual(result.saved, true);
|
||||
@@ -351,7 +352,7 @@ describe('intelSnapshot', () => {
|
||||
const snapshot = JSON.parse(
|
||||
fs.readFileSync(path.join(planningDir, 'intel', '.last-refresh.json'), 'utf8')
|
||||
);
|
||||
assert.ok(snapshot.hashes['files.json']);
|
||||
assert.ok(snapshot.hashes['file-roles.json']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -379,11 +380,11 @@ describe('intelValidate', () => {
|
||||
});
|
||||
|
||||
test('reports warnings for missing _meta.updated_at', () => {
|
||||
writeIntelJson(planningDir, 'files.json', { entries: {} });
|
||||
writeIntelJson(planningDir, 'apis.json', { entries: {} });
|
||||
writeIntelJson(planningDir, 'deps.json', { entries: {} });
|
||||
writeIntelJson(planningDir, 'file-roles.json', { entries: {} });
|
||||
writeIntelJson(planningDir, 'api-map.json', { entries: {} });
|
||||
writeIntelJson(planningDir, 'dependency-graph.json', { entries: {} });
|
||||
writeIntelJson(planningDir, 'stack.json', { entries: {} });
|
||||
writeIntelMd(planningDir, 'arch.md', '# Architecture\n');
|
||||
writeIntelJson(planningDir, 'arch-decisions.json', { entries: {} });
|
||||
|
||||
const result = intelValidate(planningDir);
|
||||
assert.strictEqual(result.valid, true);
|
||||
@@ -393,11 +394,11 @@ describe('intelValidate', () => {
|
||||
test('reports invalid JSON as error', () => {
|
||||
const intelPath = path.join(planningDir, 'intel');
|
||||
fs.mkdirSync(intelPath, { recursive: true });
|
||||
fs.writeFileSync(path.join(intelPath, 'files.json'), 'not valid json', 'utf8');
|
||||
writeIntelJson(planningDir, 'apis.json', { entries: {} });
|
||||
writeIntelJson(planningDir, 'deps.json', { entries: {} });
|
||||
fs.writeFileSync(path.join(intelPath, 'file-roles.json'), 'not valid json', 'utf8');
|
||||
writeIntelJson(planningDir, 'api-map.json', { entries: {} });
|
||||
writeIntelJson(planningDir, 'dependency-graph.json', { entries: {} });
|
||||
writeIntelJson(planningDir, 'stack.json', { entries: {} });
|
||||
writeIntelMd(planningDir, 'arch.md', '# Architecture\n');
|
||||
writeIntelJson(planningDir, 'arch-decisions.json', { entries: {} });
|
||||
|
||||
const result = intelValidate(planningDir);
|
||||
assert.strictEqual(result.valid, false);
|
||||
@@ -406,15 +407,15 @@ describe('intelValidate', () => {
|
||||
|
||||
test('passes validation with complete fresh intel', () => {
|
||||
const now = new Date().toISOString();
|
||||
writeIntelJson(planningDir, 'files.json', {
|
||||
writeIntelJson(planningDir, 'file-roles.json', {
|
||||
_meta: { updated_at: now },
|
||||
entries: {},
|
||||
});
|
||||
writeIntelJson(planningDir, 'apis.json', {
|
||||
writeIntelJson(planningDir, 'api-map.json', {
|
||||
_meta: { updated_at: now },
|
||||
entries: {},
|
||||
});
|
||||
writeIntelJson(planningDir, 'deps.json', {
|
||||
writeIntelJson(planningDir, 'dependency-graph.json', {
|
||||
_meta: { updated_at: now },
|
||||
entries: {},
|
||||
});
|
||||
@@ -422,7 +423,10 @@ describe('intelValidate', () => {
|
||||
_meta: { updated_at: now },
|
||||
entries: {},
|
||||
});
|
||||
writeIntelMd(planningDir, 'arch.md', '# Architecture\n');
|
||||
writeIntelJson(planningDir, 'arch-decisions.json', {
|
||||
_meta: { updated_at: now },
|
||||
entries: {},
|
||||
});
|
||||
|
||||
const result = intelValidate(planningDir);
|
||||
assert.strictEqual(result.valid, true);
|
||||
@@ -446,12 +450,12 @@ describe('intelPatchMeta', () => {
|
||||
});
|
||||
|
||||
test('patches _meta.updated_at and increments version', () => {
|
||||
writeIntelJson(planningDir, 'files.json', {
|
||||
writeIntelJson(planningDir, 'file-roles.json', {
|
||||
_meta: { updated_at: '2025-01-01T00:00:00Z', version: 1 },
|
||||
entries: {},
|
||||
});
|
||||
|
||||
const filePath = path.join(planningDir, 'intel', 'files.json');
|
||||
const filePath = path.join(planningDir, 'intel', 'file-roles.json');
|
||||
const result = intelPatchMeta(filePath);
|
||||
|
||||
assert.strictEqual(result.patched, true);
|
||||
@@ -462,9 +466,9 @@ describe('intelPatchMeta', () => {
|
||||
});
|
||||
|
||||
test('creates _meta if missing', () => {
|
||||
writeIntelJson(planningDir, 'files.json', { entries: {} });
|
||||
writeIntelJson(planningDir, 'file-roles.json', { entries: {} });
|
||||
|
||||
const filePath = path.join(planningDir, 'intel', 'files.json');
|
||||
const filePath = path.join(planningDir, 'intel', 'file-roles.json');
|
||||
const result = intelPatchMeta(filePath);
|
||||
|
||||
assert.strictEqual(result.patched, true);
|
||||
|
||||
Reference in New Issue
Block a user