@context-forge/core 0.3.3 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config/ConfigKeys.d.ts.map +1 -1
- package/dist/config/ConfigKeys.js +10 -0
- package/dist/config/ConfigKeys.js.map +1 -1
- package/dist/guides/GuideManager.d.ts +2 -0
- package/dist/guides/GuideManager.d.ts.map +1 -1
- package/dist/guides/GuideManager.js +18 -1
- package/dist/guides/GuideManager.js.map +1 -1
- package/dist/introspection/ArtifactIntrospector.d.ts.map +1 -1
- package/dist/introspection/ArtifactIntrospector.js +18 -16
- package/dist/introspection/ArtifactIntrospector.js.map +1 -1
- package/dist/introspection/ConsistencyChecker.d.ts +38 -0
- package/dist/introspection/ConsistencyChecker.d.ts.map +1 -0
- package/dist/introspection/ConsistencyChecker.js +381 -0
- package/dist/introspection/ConsistencyChecker.js.map +1 -0
- package/dist/introspection/WorkflowNavigator.d.ts +30 -0
- package/dist/introspection/WorkflowNavigator.d.ts.map +1 -0
- package/dist/introspection/WorkflowNavigator.js +265 -0
- package/dist/introspection/WorkflowNavigator.js.map +1 -0
- package/dist/introspection/parsers/slicePlanParser.d.ts +3 -0
- package/dist/introspection/parsers/slicePlanParser.d.ts.map +1 -1
- package/dist/introspection/parsers/slicePlanParser.js +34 -14
- package/dist/introspection/parsers/slicePlanParser.js.map +1 -1
- package/dist/introspection/types.d.ts +74 -0
- package/dist/introspection/types.d.ts.map +1 -1
- package/dist/introspection/writers/markdownWriter.d.ts +12 -0
- package/dist/introspection/writers/markdownWriter.d.ts.map +1 -0
- package/dist/introspection/writers/markdownWriter.js +93 -0
- package/dist/introspection/writers/markdownWriter.js.map +1 -0
- package/dist/node.d.ts +4 -1
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +4 -1
- package/dist/node.js.map +1 -1
- package/dist/schema/resolveFileByIndex.d.ts +11 -0
- package/dist/schema/resolveFileByIndex.d.ts.map +1 -1
- package/dist/schema/resolveFileByIndex.js +38 -0
- package/dist/schema/resolveFileByIndex.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConfigKeys.d.ts","sourceRoot":"","sources":["../../src/config/ConfigKeys.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;IACtC,OAAO,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;CAChE;AAED,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,
|
|
1
|
+
{"version":3,"file":"ConfigKeys.d.ts","sourceRoot":"","sources":["../../src/config/ConfigKeys.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;IACtC,OAAO,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;CAChE;AAED,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAgC3D,CAAC"}
|
|
@@ -20,5 +20,15 @@ export const CONFIG_KEYS = {
|
|
|
20
20
|
description: 'Strategy for managing the AI project guide via git',
|
|
21
21
|
enum: ['submodule', 'clone', 'manual'],
|
|
22
22
|
},
|
|
23
|
+
'workflow.auto_advance': {
|
|
24
|
+
type: 'boolean',
|
|
25
|
+
default: false,
|
|
26
|
+
description: 'Auto-advance to next slice when current is complete',
|
|
27
|
+
},
|
|
28
|
+
'workflow.auto_fix': {
|
|
29
|
+
type: 'boolean',
|
|
30
|
+
default: false,
|
|
31
|
+
description: 'Automatically apply non-destructive corrections when running consistency checks',
|
|
32
|
+
},
|
|
23
33
|
};
|
|
24
34
|
//# sourceMappingURL=ConfigKeys.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConfigKeys.js","sourceRoot":"","sources":["../../src/config/ConfigKeys.ts"],"names":[],"mappings":"AAQA,MAAM,CAAC,MAAM,WAAW,GAAwC;IAC9D,eAAe,EAAE;QACf,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,EAAE;QACX,WAAW,EAAE,qEAAqE;KACnF;IACD,mBAAmB,EAAE;QACnB,IAAI,EAAE,SAAS;QACf,OAAO,EAAE,KAAK;QACd,WAAW,EAAE,sDAAsD;KACpE;IACD,cAAc,EAAE;QACd,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,EAAE;QACX,WAAW,EAAE,4CAA4C;KAC1D;IACD,oBAAoB,EAAE;QACpB,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,WAAW;QACpB,WAAW,EAAE,oDAAoD;QACjE,IAAI,EAAE,CAAC,WAAW,EAAE,OAAO,EAAE,QAAQ,CAAC;KACvC;CACF,CAAC"}
|
|
1
|
+
{"version":3,"file":"ConfigKeys.js","sourceRoot":"","sources":["../../src/config/ConfigKeys.ts"],"names":[],"mappings":"AAQA,MAAM,CAAC,MAAM,WAAW,GAAwC;IAC9D,eAAe,EAAE;QACf,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,EAAE;QACX,WAAW,EAAE,qEAAqE;KACnF;IACD,mBAAmB,EAAE;QACnB,IAAI,EAAE,SAAS;QACf,OAAO,EAAE,KAAK;QACd,WAAW,EAAE,sDAAsD;KACpE;IACD,cAAc,EAAE;QACd,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,EAAE;QACX,WAAW,EAAE,4CAA4C;KAC1D;IACD,oBAAoB,EAAE;QACpB,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,WAAW;QACpB,WAAW,EAAE,oDAAoD;QACjE,IAAI,EAAE,CAAC,WAAW,EAAE,OAAO,EAAE,QAAQ,CAAC;KACvC;IACD,uBAAuB,EAAE;QACvB,IAAI,EAAE,SAAS;QACf,OAAO,EAAE,KAAK;QACd,WAAW,EAAE,qDAAqD;KACnE;IACD,mBAAmB,EAAE;QACnB,IAAI,EAAE,SAAS;QACf,OAAO,EAAE,KAAK;QACd,WAAW,EAAE,iFAAiF;KAC/F;CACF,CAAC"}
|
|
@@ -15,6 +15,8 @@ export declare class GuideManager {
|
|
|
15
15
|
private resolveSource;
|
|
16
16
|
/** Resolve strategy from config or default */
|
|
17
17
|
private resolveStrategy;
|
|
18
|
+
/** Create user artifact directories alongside the guide */
|
|
19
|
+
private createUserDirectories;
|
|
18
20
|
/** Map method name to strategy instance */
|
|
19
21
|
private getStrategy;
|
|
20
22
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GuideManager.d.ts","sourceRoot":"","sources":["../../src/guides/GuideManager.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"GuideManager.d.ts","sourceRoot":"","sources":["../../src/guides/GuideManager.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,aAAa,EAAE,YAAY,EAAmB,MAAM,YAAY,CAAC;AAOvG,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAgB;IAC/C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgB;gBAE7B,WAAW,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,aAAa;IAM9D,4CAA4C;IACtC,MAAM,IAAI,OAAO,CAAC,SAAS,CAAC;IAKlC,yCAAyC;IACnC,OAAO,CAAC,gBAAgB,CAAC,EAAE,WAAW,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAsB9F,4CAA4C;IACtC,MAAM,IAAI,OAAO,CAAC,YAAY,CAAC;IAerC,gDAAgD;YAClC,aAAa;IAc3B,8CAA8C;YAChC,eAAe;IAc7B,2DAA2D;IAC3D,OAAO,CAAC,qBAAqB;IAa7B,2CAA2C;IAC3C,OAAO,CAAC,WAAW;CAUpB"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Orchestration layer for guide lifecycle management
|
|
2
2
|
import { join } from 'path';
|
|
3
|
+
import { mkdirSync } from 'fs';
|
|
3
4
|
import { DEFAULT_SOURCE_GIT, GUIDE_RELATIVE_PATH } from './types.js';
|
|
4
5
|
import { GuideDetector } from './GuideDetector.js';
|
|
5
6
|
import { SubmoduleStrategy } from './strategies/SubmoduleStrategy.js';
|
|
@@ -30,7 +31,10 @@ export class GuideManager {
|
|
|
30
31
|
throw new Error('Guide is already installed. Use guide_update (or cf guides update) to update it.');
|
|
31
32
|
}
|
|
32
33
|
const strategy = this.getStrategy(method);
|
|
33
|
-
|
|
34
|
+
const result = await strategy.install(this.projectPath, source, targetDir);
|
|
35
|
+
// Create user artifact directories so the project is ready to use
|
|
36
|
+
this.createUserDirectories();
|
|
37
|
+
return result;
|
|
34
38
|
}
|
|
35
39
|
/** Update an existing guide installation */
|
|
36
40
|
async update() {
|
|
@@ -73,6 +77,19 @@ export class GuideManager {
|
|
|
73
77
|
}
|
|
74
78
|
return 'submodule';
|
|
75
79
|
}
|
|
80
|
+
/** Create user artifact directories alongside the guide */
|
|
81
|
+
createUserDirectories() {
|
|
82
|
+
const userDirs = [
|
|
83
|
+
'project-documents/user',
|
|
84
|
+
'project-documents/user/architecture',
|
|
85
|
+
'project-documents/user/slices',
|
|
86
|
+
'project-documents/user/tasks',
|
|
87
|
+
'project-documents/user/project-guides',
|
|
88
|
+
];
|
|
89
|
+
for (const dir of userDirs) {
|
|
90
|
+
mkdirSync(join(this.projectPath, dir), { recursive: true });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
76
93
|
/** Map method name to strategy instance */
|
|
77
94
|
getStrategy(method) {
|
|
78
95
|
switch (method) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GuideManager.js","sourceRoot":"","sources":["../../src/guides/GuideManager.ts"],"names":[],"mappings":"AAAA,qDAAqD;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"GuideManager.js","sourceRoot":"","sources":["../../src/guides/GuideManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ArtifactIntrospector.d.ts","sourceRoot":"","sources":["../../src/introspection/ArtifactIntrospector.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,KAAK,EACV,eAAe,EACf,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,uBAAuB,EACvB,oBAAoB,EACrB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"ArtifactIntrospector.d.ts","sourceRoot":"","sources":["../../src/introspection/ArtifactIntrospector.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,KAAK,EACV,eAAe,EACf,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,uBAAuB,EACvB,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAQpB;;;GAGG;AACH,qBAAa,oBAAqB,YAAW,qBAAqB;IAC1D,cAAc,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAI/D,aAAa,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC;IAIxE,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAI9D,eAAe,CAAC,aAAa,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAIrF,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,CAAC;IAIhG;;;OAGG;IACG,SAAS,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,oBAAoB,CAAC;CA2FrE"}
|
|
@@ -4,6 +4,7 @@ import { parseTaskFile } from './parsers/taskFileParser.js';
|
|
|
4
4
|
import { parseFrontmatter } from './parsers/frontmatterParser.js';
|
|
5
5
|
import { parseFutureWork } from './parsers/futureWorkParser.js';
|
|
6
6
|
import { detectDocuments, checkFileExists } from './parsers/documentDetector.js';
|
|
7
|
+
import { resolveArtifactPath } from '../schema/resolveFileByIndex.js';
|
|
7
8
|
/**
|
|
8
9
|
* Orchestrator that delegates to individual parser functions.
|
|
9
10
|
* Each method resolves file paths from projectPath as needed.
|
|
@@ -45,9 +46,10 @@ export class ArtifactIntrospector {
|
|
|
45
46
|
// Parse slice plan if available
|
|
46
47
|
try {
|
|
47
48
|
if (project.fileSlicePlan) {
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
|
|
49
|
+
const planRelPath = resolveArtifactPath('fileSlicePlan', project.fileSlicePlan);
|
|
50
|
+
const planPath = planRelPath ? join(projectPath, planRelPath) : null;
|
|
51
|
+
const planResult = planPath ? await parseSlicePlan(planPath) : null;
|
|
52
|
+
if (planResult && planResult.totalSlices > 0) {
|
|
51
53
|
summary.slicePlan = {
|
|
52
54
|
totalSlices: planResult.totalSlices,
|
|
53
55
|
completedSlices: planResult.completedSlices,
|
|
@@ -89,29 +91,29 @@ export class ArtifactIntrospector {
|
|
|
89
91
|
catch {
|
|
90
92
|
// Task parsing failed — continue with other operations
|
|
91
93
|
}
|
|
92
|
-
// Check artifact existence
|
|
94
|
+
// Check artifact existence (resolve stems to full relative paths)
|
|
95
|
+
const checkArtifact = async (field, stem) => {
|
|
96
|
+
if (!stem)
|
|
97
|
+
return false;
|
|
98
|
+
const relPath = resolveArtifactPath(field, stem);
|
|
99
|
+
if (!relPath)
|
|
100
|
+
return false;
|
|
101
|
+
return checkFileExists(projectPath, relPath);
|
|
102
|
+
};
|
|
93
103
|
try {
|
|
94
|
-
|
|
95
|
-
summary.artifacts.hasSlicePlan = await checkFileExists(projectPath, project.fileSlicePlan);
|
|
96
|
-
}
|
|
104
|
+
summary.artifacts.hasSlicePlan = await checkArtifact('fileSlicePlan', project.fileSlicePlan);
|
|
97
105
|
}
|
|
98
106
|
catch { /* continue */ }
|
|
99
107
|
try {
|
|
100
|
-
|
|
101
|
-
summary.artifacts.hasHLD = await checkFileExists(projectPath, project.fileHLD);
|
|
102
|
-
}
|
|
108
|
+
summary.artifacts.hasHLD = await checkArtifact('fileHLD', project.fileHLD);
|
|
103
109
|
}
|
|
104
110
|
catch { /* continue */ }
|
|
105
111
|
try {
|
|
106
|
-
|
|
107
|
-
summary.artifacts.hasArch = await checkFileExists(projectPath, project.fileArch);
|
|
108
|
-
}
|
|
112
|
+
summary.artifacts.hasArch = await checkArtifact('fileArch', project.fileArch);
|
|
109
113
|
}
|
|
110
114
|
catch { /* continue */ }
|
|
111
115
|
try {
|
|
112
|
-
|
|
113
|
-
summary.artifacts.hasSpec = await checkFileExists(projectPath, project.fileSpec);
|
|
114
|
-
}
|
|
116
|
+
summary.artifacts.hasSpec = await checkArtifact('fileSpec', project.fileSpec);
|
|
115
117
|
}
|
|
116
118
|
catch { /* continue */ }
|
|
117
119
|
// Check current slice design and task file existence
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ArtifactIntrospector.js","sourceRoot":"","sources":["../../src/introspection/ArtifactIntrospector.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAWjC,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"ArtifactIntrospector.js","sourceRoot":"","sources":["../../src/introspection/ArtifactIntrospector.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ProjectData } from '../types/project.js';
|
|
2
|
+
import type { IArtifactIntrospector } from './interfaces.js';
|
|
3
|
+
import type { ConsistencyCheckResult, ConsistencyFixResult } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Compares related artifact states within a project and flags mismatches.
|
|
6
|
+
* Optionally applies non-destructive corrections to fixable findings.
|
|
7
|
+
*/
|
|
8
|
+
export declare class ConsistencyChecker {
|
|
9
|
+
private readonly introspector;
|
|
10
|
+
constructor(introspector: IArtifactIntrospector);
|
|
11
|
+
/**
|
|
12
|
+
* Run all detection rules against a project and return structured findings.
|
|
13
|
+
*/
|
|
14
|
+
check(project: ProjectData): Promise<ConsistencyCheckResult>;
|
|
15
|
+
/**
|
|
16
|
+
* Run check(), then apply non-destructive corrections to fixable findings.
|
|
17
|
+
*/
|
|
18
|
+
fix(project: ProjectData): Promise<ConsistencyFixResult>;
|
|
19
|
+
/** Rule 1: Task completion vs. slice plan checkbox */
|
|
20
|
+
private ruleTaskVsPlan;
|
|
21
|
+
/** Rule 2: Frontmatter status vs. computed state */
|
|
22
|
+
private ruleFrontmatterVsComputed;
|
|
23
|
+
/** Rule 3: Missing artifact cross-references */
|
|
24
|
+
private ruleMissingArtifacts;
|
|
25
|
+
/** Rule 4: Plan checkbox vs. slice frontmatter status */
|
|
26
|
+
private rulePlanVsFrontmatter;
|
|
27
|
+
/** Rule 5: Task file frontmatter status vs. computed task completion */
|
|
28
|
+
private ruleTaskFileStatus;
|
|
29
|
+
private emptyResult;
|
|
30
|
+
private buildResult;
|
|
31
|
+
private resolveSlicePlanPath;
|
|
32
|
+
private safeDetectDocuments;
|
|
33
|
+
private safeParseSlicePlan;
|
|
34
|
+
private safeParseTaskFile;
|
|
35
|
+
private safeParseFrontmatter;
|
|
36
|
+
private safeParseTaskFileFrontmatter;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=ConsistencyChecker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConsistencyChecker.d.ts","sourceRoot":"","sources":["../../src/introspection/ConsistencyChecker.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,KAAK,EAEV,sBAAsB,EACtB,oBAAoB,EAIrB,MAAM,YAAY,CAAC;AAapB;;;GAGG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAwB;gBAEzC,YAAY,EAAE,qBAAqB;IAI/C;;OAEG;IACG,KAAK,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAiElE;;OAEG;IACG,GAAG,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAqC9D,sDAAsD;IACtD,OAAO,CAAC,cAAc;IAoDtB,oDAAoD;IACpD,OAAO,CAAC,yBAAyB;IAoDjC,gDAAgD;IAChD,OAAO,CAAC,oBAAoB;IAmC5B,yDAAyD;IACzD,OAAO,CAAC,qBAAqB;IAoD7B,wEAAwE;IACxE,OAAO,CAAC,kBAAkB;IAmD1B,OAAO,CAAC,WAAW;IAYnB,OAAO,CAAC,WAAW;IAoBnB,OAAO,CAAC,oBAAoB;YAMd,mBAAmB;YAYnB,kBAAkB;YAalB,iBAAiB;YAajB,oBAAoB;YAYpB,4BAA4B;CAY3C"}
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { resolveArtifactPath } from '../schema/resolveFileByIndex.js';
|
|
3
|
+
import { updateCheckbox, updateFrontmatterField } from './writers/markdownWriter.js';
|
|
4
|
+
/**
|
|
5
|
+
* Extract numeric slice index from a fileSlice value like "165-slice.workflow-navigator".
|
|
6
|
+
*/
|
|
7
|
+
function extractSliceIndex(fileSlice) {
|
|
8
|
+
if (!fileSlice)
|
|
9
|
+
return null;
|
|
10
|
+
const match = /^(\d+)-/.exec(fileSlice);
|
|
11
|
+
return match ? parseInt(match[1], 10) : null;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Compares related artifact states within a project and flags mismatches.
|
|
15
|
+
* Optionally applies non-destructive corrections to fixable findings.
|
|
16
|
+
*/
|
|
17
|
+
export class ConsistencyChecker {
|
|
18
|
+
introspector;
|
|
19
|
+
constructor(introspector) {
|
|
20
|
+
this.introspector = introspector;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Run all detection rules against a project and return structured findings.
|
|
24
|
+
*/
|
|
25
|
+
async check(project) {
|
|
26
|
+
const projectPath = project.projectPath;
|
|
27
|
+
if (!projectPath) {
|
|
28
|
+
return this.emptyResult('');
|
|
29
|
+
}
|
|
30
|
+
const sliceIndex = extractSliceIndex(project.fileSlice);
|
|
31
|
+
if (sliceIndex === null) {
|
|
32
|
+
return this.emptyResult(projectPath);
|
|
33
|
+
}
|
|
34
|
+
// Gather data from introspector — each call individually try/caught
|
|
35
|
+
const docs = await this.safeDetectDocuments(projectPath, sliceIndex);
|
|
36
|
+
const slicePlanResult = await this.safeParseSlicePlan(project, projectPath);
|
|
37
|
+
const taskResult = await this.safeParseTaskFile(docs?.taskFile, projectPath);
|
|
38
|
+
const sliceFrontmatter = await this.safeParseFrontmatter(docs?.sliceDesign, projectPath);
|
|
39
|
+
// Find the matching slice plan entry
|
|
40
|
+
const planEntry = slicePlanResult?.entries.find((e) => e.index === sliceIndex) ?? null;
|
|
41
|
+
const slicePlanPath = this.resolveSlicePlanPath(project, projectPath);
|
|
42
|
+
const findings = [];
|
|
43
|
+
// Rule 1: task completion vs. slice plan checkbox
|
|
44
|
+
findings.push(...this.ruleTaskVsPlan(taskResult, planEntry, slicePlanPath, slicePlanResult, sliceIndex));
|
|
45
|
+
const sliceDesignRel = docs?.sliceDesign ?? null;
|
|
46
|
+
// Rule 2: frontmatter status vs. computed state
|
|
47
|
+
findings.push(...this.ruleFrontmatterVsComputed(sliceFrontmatter, taskResult, sliceDesignRel, projectPath));
|
|
48
|
+
// Rule 3: missing artifact cross-references
|
|
49
|
+
findings.push(...this.ruleMissingArtifacts(docs, planEntry, sliceIndex));
|
|
50
|
+
// Rule 4: plan checkbox vs. slice frontmatter status
|
|
51
|
+
findings.push(...this.rulePlanVsFrontmatter(planEntry, sliceFrontmatter, slicePlanPath, sliceDesignRel, projectPath));
|
|
52
|
+
// Rule 5: task file frontmatter status vs. computed task completion
|
|
53
|
+
const taskFileFrontmatter = await this.safeParseTaskFileFrontmatter(docs?.taskFile, projectPath);
|
|
54
|
+
findings.push(...this.ruleTaskFileStatus(taskResult, taskFileFrontmatter));
|
|
55
|
+
return this.buildResult(projectPath, findings);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Run check(), then apply non-destructive corrections to fixable findings.
|
|
59
|
+
*/
|
|
60
|
+
async fix(project) {
|
|
61
|
+
const checkResult = await this.check(project);
|
|
62
|
+
const fixLog = [];
|
|
63
|
+
const fixErrors = [];
|
|
64
|
+
let fixed = 0;
|
|
65
|
+
for (const finding of checkResult.findings) {
|
|
66
|
+
if (!finding.fixable || !finding.fixAction)
|
|
67
|
+
continue;
|
|
68
|
+
try {
|
|
69
|
+
if (finding.fixAction.type === 'update-checkbox') {
|
|
70
|
+
const { lineIndex, checked } = finding.fixAction.detail;
|
|
71
|
+
const entry = await updateCheckbox(finding.fixAction.filePath, lineIndex, checked);
|
|
72
|
+
entry.rule = finding.rule;
|
|
73
|
+
fixLog.push(entry);
|
|
74
|
+
fixed++;
|
|
75
|
+
}
|
|
76
|
+
else if (finding.fixAction.type === 'update-frontmatter') {
|
|
77
|
+
const { key, value } = finding.fixAction.detail;
|
|
78
|
+
const entry = await updateFrontmatterField(finding.fixAction.filePath, key, value);
|
|
79
|
+
entry.rule = finding.rule;
|
|
80
|
+
fixLog.push(entry);
|
|
81
|
+
fixed++;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
86
|
+
fixErrors.push(`Fix failed for ${finding.rule}: ${msg}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return { ...checkResult, fixed, fixLog, fixErrors };
|
|
90
|
+
}
|
|
91
|
+
// --- Detection Rules ---
|
|
92
|
+
/** Rule 1: Task completion vs. slice plan checkbox */
|
|
93
|
+
ruleTaskVsPlan(taskResult, planEntry, slicePlanPath, slicePlanResult, sliceIndex) {
|
|
94
|
+
const findings = [];
|
|
95
|
+
if (!taskResult || !planEntry || !slicePlanPath || !slicePlanResult)
|
|
96
|
+
return findings;
|
|
97
|
+
const tasksComplete = taskResult.inferredStatus === 'complete';
|
|
98
|
+
const sliceChecked = planEntry.isChecked;
|
|
99
|
+
if (tasksComplete && !sliceChecked) {
|
|
100
|
+
// Find the line index of this entry in the plan file for fixing
|
|
101
|
+
const entryIdx = slicePlanResult.entries.findIndex((e) => e.index === sliceIndex);
|
|
102
|
+
findings.push({
|
|
103
|
+
rule: 'task-vs-plan',
|
|
104
|
+
severity: 'warning',
|
|
105
|
+
location: slicePlanPath,
|
|
106
|
+
description: `Tasks complete (${taskResult.completedTasks}/${taskResult.totalTasks}) but slice ${sliceIndex} is unchecked in plan`,
|
|
107
|
+
suggestedFix: `Check the slice plan entry for (${sliceIndex})`,
|
|
108
|
+
fixable: true,
|
|
109
|
+
fixAction: {
|
|
110
|
+
type: 'update-checkbox',
|
|
111
|
+
filePath: slicePlanPath,
|
|
112
|
+
detail: { lineIndex: entryIdx, checked: true, entryIndex: sliceIndex },
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
if (sliceChecked && !tasksComplete) {
|
|
117
|
+
const entryIdx = slicePlanResult.entries.findIndex((e) => e.index === sliceIndex);
|
|
118
|
+
findings.push({
|
|
119
|
+
rule: 'task-vs-plan',
|
|
120
|
+
severity: 'error',
|
|
121
|
+
location: slicePlanPath,
|
|
122
|
+
description: `Slice ${sliceIndex} is checked in plan but tasks are incomplete (${taskResult.completedTasks}/${taskResult.totalTasks})`,
|
|
123
|
+
suggestedFix: `Uncheck the slice plan entry for (${sliceIndex})`,
|
|
124
|
+
fixable: true,
|
|
125
|
+
fixAction: {
|
|
126
|
+
type: 'update-checkbox',
|
|
127
|
+
filePath: slicePlanPath,
|
|
128
|
+
detail: { lineIndex: entryIdx, checked: false, entryIndex: sliceIndex },
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
return findings;
|
|
133
|
+
}
|
|
134
|
+
/** Rule 2: Frontmatter status vs. computed state */
|
|
135
|
+
ruleFrontmatterVsComputed(frontmatter, taskResult, sliceDesignRelPath, projectPath) {
|
|
136
|
+
const findings = [];
|
|
137
|
+
if (!frontmatter?.found || !frontmatter.data.status)
|
|
138
|
+
return findings;
|
|
139
|
+
if (!taskResult)
|
|
140
|
+
return findings;
|
|
141
|
+
const fmStatus = frontmatter.data.status.toLowerCase();
|
|
142
|
+
const tasksComplete = taskResult.inferredStatus === 'complete';
|
|
143
|
+
const sliceDesignFullPath = sliceDesignRelPath
|
|
144
|
+
? join(projectPath, sliceDesignRelPath)
|
|
145
|
+
: frontmatter.filePath;
|
|
146
|
+
if (fmStatus === 'complete' && !tasksComplete) {
|
|
147
|
+
findings.push({
|
|
148
|
+
rule: 'frontmatter-vs-computed',
|
|
149
|
+
severity: 'error',
|
|
150
|
+
location: sliceDesignFullPath,
|
|
151
|
+
description: `Frontmatter status is "complete" but tasks are incomplete (${taskResult.completedTasks}/${taskResult.totalTasks})`,
|
|
152
|
+
suggestedFix: 'Update frontmatter status to "in-progress"',
|
|
153
|
+
fixable: true,
|
|
154
|
+
fixAction: {
|
|
155
|
+
type: 'update-frontmatter',
|
|
156
|
+
filePath: sliceDesignFullPath,
|
|
157
|
+
detail: { key: 'status', value: 'in-progress' },
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
if ((fmStatus === 'in-progress' || fmStatus === 'not-started') && tasksComplete) {
|
|
162
|
+
findings.push({
|
|
163
|
+
rule: 'frontmatter-vs-computed',
|
|
164
|
+
severity: 'warning',
|
|
165
|
+
location: sliceDesignFullPath,
|
|
166
|
+
description: `Frontmatter status is "${fmStatus}" but all tasks are complete (${taskResult.completedTasks}/${taskResult.totalTasks})`,
|
|
167
|
+
suggestedFix: 'Update frontmatter status to "complete"',
|
|
168
|
+
fixable: true,
|
|
169
|
+
fixAction: {
|
|
170
|
+
type: 'update-frontmatter',
|
|
171
|
+
filePath: sliceDesignFullPath,
|
|
172
|
+
detail: { key: 'status', value: 'complete' },
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
return findings;
|
|
177
|
+
}
|
|
178
|
+
/** Rule 3: Missing artifact cross-references */
|
|
179
|
+
ruleMissingArtifacts(docs, planEntry, sliceIndex) {
|
|
180
|
+
const findings = [];
|
|
181
|
+
const hasTaskFile = docs?.taskFile !== null && docs?.taskFile !== undefined;
|
|
182
|
+
const hasPlanEntry = planEntry !== null;
|
|
183
|
+
if (hasTaskFile && !hasPlanEntry) {
|
|
184
|
+
findings.push({
|
|
185
|
+
rule: 'missing-artifact',
|
|
186
|
+
severity: 'info',
|
|
187
|
+
location: docs.taskFile[0],
|
|
188
|
+
description: `Task file exists for slice ${sliceIndex} but no matching slice plan entry found`,
|
|
189
|
+
suggestedFix: 'Add an entry for this slice to the slice plan',
|
|
190
|
+
fixable: false,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
if (hasPlanEntry && !hasTaskFile) {
|
|
194
|
+
findings.push({
|
|
195
|
+
rule: 'missing-artifact',
|
|
196
|
+
severity: 'info',
|
|
197
|
+
location: `slice plan entry ${sliceIndex}`,
|
|
198
|
+
description: `Slice plan entry exists for ${sliceIndex} (${planEntry.name}) but no task file found`,
|
|
199
|
+
suggestedFix: 'Create a task file when ready to begin implementation',
|
|
200
|
+
fixable: false,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
return findings;
|
|
204
|
+
}
|
|
205
|
+
/** Rule 4: Plan checkbox vs. slice frontmatter status */
|
|
206
|
+
rulePlanVsFrontmatter(planEntry, frontmatter, slicePlanPath, sliceDesignRelPath, projectPath) {
|
|
207
|
+
const findings = [];
|
|
208
|
+
if (!planEntry || !frontmatter?.found || !frontmatter.data.status)
|
|
209
|
+
return findings;
|
|
210
|
+
const fmStatus = frontmatter.data.status.toLowerCase();
|
|
211
|
+
const planChecked = planEntry.isChecked;
|
|
212
|
+
const sliceDesignFullPath = sliceDesignRelPath
|
|
213
|
+
? join(projectPath, sliceDesignRelPath)
|
|
214
|
+
: frontmatter.filePath;
|
|
215
|
+
if (planChecked && fmStatus !== 'complete') {
|
|
216
|
+
findings.push({
|
|
217
|
+
rule: 'plan-vs-frontmatter',
|
|
218
|
+
severity: 'warning',
|
|
219
|
+
location: sliceDesignFullPath,
|
|
220
|
+
description: `Slice plan entry is checked but frontmatter status is "${fmStatus}"`,
|
|
221
|
+
suggestedFix: 'Update frontmatter status to "complete"',
|
|
222
|
+
fixable: true,
|
|
223
|
+
fixAction: {
|
|
224
|
+
type: 'update-frontmatter',
|
|
225
|
+
filePath: sliceDesignFullPath,
|
|
226
|
+
detail: { key: 'status', value: 'complete' },
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
if (!planChecked && fmStatus === 'complete' && slicePlanPath) {
|
|
231
|
+
findings.push({
|
|
232
|
+
rule: 'plan-vs-frontmatter',
|
|
233
|
+
severity: 'warning',
|
|
234
|
+
location: slicePlanPath,
|
|
235
|
+
description: `Frontmatter status is "complete" but slice plan entry is unchecked`,
|
|
236
|
+
suggestedFix: 'Check the slice plan entry',
|
|
237
|
+
fixable: true,
|
|
238
|
+
fixAction: {
|
|
239
|
+
type: 'update-checkbox',
|
|
240
|
+
filePath: slicePlanPath,
|
|
241
|
+
detail: { lineIndex: -1, checked: true },
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
return findings;
|
|
246
|
+
}
|
|
247
|
+
/** Rule 5: Task file frontmatter status vs. computed task completion */
|
|
248
|
+
ruleTaskFileStatus(taskResult, taskFrontmatter) {
|
|
249
|
+
const findings = [];
|
|
250
|
+
if (!taskResult || !taskFrontmatter?.found || !taskFrontmatter.data.status)
|
|
251
|
+
return findings;
|
|
252
|
+
const fmStatus = taskFrontmatter.data.status.toLowerCase().replace(/_/g, '-');
|
|
253
|
+
const computed = taskResult.inferredStatus; // 'complete' | 'in-progress' | 'not-started'
|
|
254
|
+
const taskFilePath = taskFrontmatter.filePath;
|
|
255
|
+
// Map task file status values to normalized form for comparison
|
|
256
|
+
// Task files use not_started/in_progress/complete in frontmatter
|
|
257
|
+
if (fmStatus === 'complete' && computed !== 'complete') {
|
|
258
|
+
findings.push({
|
|
259
|
+
rule: 'task-file-status',
|
|
260
|
+
severity: 'error',
|
|
261
|
+
location: taskFilePath,
|
|
262
|
+
description: `Task file status is "complete" but tasks are incomplete (${taskResult.completedTasks}/${taskResult.totalTasks})`,
|
|
263
|
+
suggestedFix: 'Update task file status to "in_progress"',
|
|
264
|
+
fixable: true,
|
|
265
|
+
fixAction: {
|
|
266
|
+
type: 'update-frontmatter',
|
|
267
|
+
filePath: taskFilePath,
|
|
268
|
+
detail: { key: 'status', value: 'in_progress' },
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
if (fmStatus !== 'complete' && computed === 'complete') {
|
|
273
|
+
findings.push({
|
|
274
|
+
rule: 'task-file-status',
|
|
275
|
+
severity: 'warning',
|
|
276
|
+
location: taskFilePath,
|
|
277
|
+
description: `Task file status is "${taskFrontmatter.data.status}" but all tasks are complete (${taskResult.completedTasks}/${taskResult.totalTasks})`,
|
|
278
|
+
suggestedFix: 'Update task file status to "complete"',
|
|
279
|
+
fixable: true,
|
|
280
|
+
fixAction: {
|
|
281
|
+
type: 'update-frontmatter',
|
|
282
|
+
filePath: taskFilePath,
|
|
283
|
+
detail: { key: 'status', value: 'complete' },
|
|
284
|
+
},
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
return findings;
|
|
288
|
+
}
|
|
289
|
+
// --- Helper methods ---
|
|
290
|
+
emptyResult(projectPath) {
|
|
291
|
+
return {
|
|
292
|
+
projectPath,
|
|
293
|
+
findings: [],
|
|
294
|
+
totalFindings: 0,
|
|
295
|
+
errors: 0,
|
|
296
|
+
warnings: 0,
|
|
297
|
+
infos: 0,
|
|
298
|
+
summary: 'No inconsistencies found',
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
buildResult(projectPath, findings) {
|
|
302
|
+
const errors = findings.filter((f) => f.severity === 'error').length;
|
|
303
|
+
const warnings = findings.filter((f) => f.severity === 'warning').length;
|
|
304
|
+
const infos = findings.filter((f) => f.severity === 'info').length;
|
|
305
|
+
const total = findings.length;
|
|
306
|
+
let summary;
|
|
307
|
+
if (total === 0) {
|
|
308
|
+
summary = 'No inconsistencies found';
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
const parts = [];
|
|
312
|
+
if (errors > 0)
|
|
313
|
+
parts.push(`${errors} error${errors !== 1 ? 's' : ''}`);
|
|
314
|
+
if (warnings > 0)
|
|
315
|
+
parts.push(`${warnings} warning${warnings !== 1 ? 's' : ''}`);
|
|
316
|
+
if (infos > 0)
|
|
317
|
+
parts.push(`${infos} info${infos !== 1 ? 's' : ''}`);
|
|
318
|
+
summary = `${total} finding${total !== 1 ? 's' : ''}: ${parts.join(', ')}`;
|
|
319
|
+
}
|
|
320
|
+
return { projectPath, findings, totalFindings: total, errors, warnings, infos, summary };
|
|
321
|
+
}
|
|
322
|
+
resolveSlicePlanPath(project, projectPath) {
|
|
323
|
+
if (!project.fileSlicePlan)
|
|
324
|
+
return null;
|
|
325
|
+
const relPath = resolveArtifactPath('fileSlicePlan', project.fileSlicePlan);
|
|
326
|
+
return relPath ? join(projectPath, relPath) : null;
|
|
327
|
+
}
|
|
328
|
+
async safeDetectDocuments(projectPath, sliceIndex) {
|
|
329
|
+
try {
|
|
330
|
+
const docs = await this.introspector.detectDocuments(projectPath, sliceIndex);
|
|
331
|
+
return { taskFile: docs.taskFile, sliceDesign: docs.sliceDesign };
|
|
332
|
+
}
|
|
333
|
+
catch {
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
async safeParseSlicePlan(project, projectPath) {
|
|
338
|
+
const planPath = this.resolveSlicePlanPath(project, projectPath);
|
|
339
|
+
if (!planPath)
|
|
340
|
+
return null;
|
|
341
|
+
try {
|
|
342
|
+
return await this.introspector.parseSlicePlan(planPath);
|
|
343
|
+
}
|
|
344
|
+
catch {
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
async safeParseTaskFile(taskFilePaths, projectPath) {
|
|
349
|
+
if (!taskFilePaths || taskFilePaths.length === 0)
|
|
350
|
+
return null;
|
|
351
|
+
try {
|
|
352
|
+
const fullPaths = taskFilePaths.map((p) => join(projectPath, p));
|
|
353
|
+
return await this.introspector.parseTaskFile(fullPaths);
|
|
354
|
+
}
|
|
355
|
+
catch {
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
async safeParseFrontmatter(sliceDesignRelPath, projectPath) {
|
|
360
|
+
if (!sliceDesignRelPath)
|
|
361
|
+
return null;
|
|
362
|
+
try {
|
|
363
|
+
return await this.introspector.parseFrontmatter(join(projectPath, sliceDesignRelPath));
|
|
364
|
+
}
|
|
365
|
+
catch {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
async safeParseTaskFileFrontmatter(taskFilePaths, projectPath) {
|
|
370
|
+
if (!taskFilePaths || taskFilePaths.length === 0)
|
|
371
|
+
return null;
|
|
372
|
+
// Parse frontmatter from the first task file (primary file has the frontmatter)
|
|
373
|
+
try {
|
|
374
|
+
return await this.introspector.parseFrontmatter(join(projectPath, taskFilePaths[0]));
|
|
375
|
+
}
|
|
376
|
+
catch {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
//# sourceMappingURL=ConsistencyChecker.js.map
|