@context-forge/core 0.8.6 → 0.9.0
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 +55 -0
- package/dist/config/ConfigKeys.js.map +1 -1
- package/dist/introspection/ConsistencyChecker.d.ts +24 -1
- package/dist/introspection/ConsistencyChecker.d.ts.map +1 -1
- package/dist/introspection/ConsistencyChecker.js +191 -31
- package/dist/introspection/ConsistencyChecker.js.map +1 -1
- package/dist/introspection/ProjectModelBuilder.d.ts.map +1 -1
- package/dist/introspection/ProjectModelBuilder.js +27 -10
- package/dist/introspection/ProjectModelBuilder.js.map +1 -1
- package/dist/introspection/WorkflowNavigator.d.ts +34 -1
- package/dist/introspection/WorkflowNavigator.d.ts.map +1 -1
- package/dist/introspection/WorkflowNavigator.js +231 -52
- package/dist/introspection/WorkflowNavigator.js.map +1 -1
- package/dist/introspection/index.d.ts +2 -0
- package/dist/introspection/index.d.ts.map +1 -1
- package/dist/introspection/index.js +2 -0
- package/dist/introspection/index.js.map +1 -1
- package/dist/introspection/parsers/documentDetector.d.ts +1 -1
- package/dist/introspection/parsers/documentDetector.d.ts.map +1 -1
- package/dist/introspection/parsers/documentDetector.js +30 -10
- package/dist/introspection/parsers/documentDetector.js.map +1 -1
- package/dist/introspection/parsers/slicePlanParser.d.ts.map +1 -1
- package/dist/introspection/parsers/slicePlanParser.js +5 -2
- package/dist/introspection/parsers/slicePlanParser.js.map +1 -1
- package/dist/introspection/parsers/statusNormalizer.d.ts +10 -2
- package/dist/introspection/parsers/statusNormalizer.d.ts.map +1 -1
- package/dist/introspection/parsers/statusNormalizer.js +25 -16
- package/dist/introspection/parsers/statusNormalizer.js.map +1 -1
- package/dist/introspection/parsers/taskFileParser.d.ts +6 -2
- package/dist/introspection/parsers/taskFileParser.d.ts.map +1 -1
- package/dist/introspection/parsers/taskFileParser.js +27 -18
- package/dist/introspection/parsers/taskFileParser.js.map +1 -1
- package/dist/introspection/reviewGate.d.ts +62 -0
- package/dist/introspection/reviewGate.d.ts.map +1 -0
- package/dist/introspection/reviewGate.js +164 -0
- package/dist/introspection/reviewGate.js.map +1 -0
- package/dist/introspection/statusDerivation.d.ts +23 -0
- package/dist/introspection/statusDerivation.d.ts.map +1 -0
- package/dist/introspection/statusDerivation.js +19 -0
- package/dist/introspection/statusDerivation.js.map +1 -0
- package/dist/introspection/types.d.ts +22 -3
- package/dist/introspection/types.d.ts.map +1 -1
- package/dist/introspection/types.js +7 -1
- package/dist/introspection/types.js.map +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +1 -1
- package/dist/node.js.map +1 -1
- package/dist/schema/frontmatterSchema.d.ts.map +1 -1
- package/dist/schema/frontmatterSchema.js +4 -0
- package/dist/schema/frontmatterSchema.js.map +1 -1
- package/dist/schema/projectSchema.d.ts +4 -0
- package/dist/schema/projectSchema.d.ts.map +1 -1
- package/dist/schema/projectSchema.js +2 -0
- package/dist/schema/projectSchema.js.map +1 -1
- package/dist/schema/resolveFileByIndex.d.ts +12 -0
- package/dist/schema/resolveFileByIndex.d.ts.map +1 -1
- package/dist/schema/resolveFileByIndex.js +29 -0
- package/dist/schema/resolveFileByIndex.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slicePlanParser.d.ts","sourceRoot":"","sources":["../../../src/introspection/parsers/slicePlanParser.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"slicePlanParser.d.ts","sourceRoot":"","sources":["../../../src/introspection/parsers/slicePlanParser.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAkB,eAAe,EAAE,MAAM,aAAa,CAAC;AAqBnE;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAuE/E"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { STATUS } from '../types.js';
|
|
2
3
|
/** Matches `N. [ ] **(NNN) Slice Name** — description` — indexed format */
|
|
3
4
|
const PLAN_INDEXED_RE = /^(\d+)\.\s+\[([ xX])\]\s+\*\*\((\d+)\)\s+(.+?)\*\*\s*(.*)/;
|
|
4
5
|
/** Matches `N. [ ] **Slice Name** — description` — unindexed format (no parenthesized index) */
|
|
@@ -47,9 +48,10 @@ export async function parseSlicePlan(filePath) {
|
|
|
47
48
|
entries.push({
|
|
48
49
|
index: parseInt(indexed[3], 10),
|
|
49
50
|
name: indexed[4].trim(),
|
|
50
|
-
status: isChecked ?
|
|
51
|
+
status: isChecked ? STATUS.Complete : STATUS.NotStarted,
|
|
51
52
|
isChecked,
|
|
52
53
|
lineIndex: i,
|
|
54
|
+
indexSource: 'explicit',
|
|
53
55
|
...(description && { description }),
|
|
54
56
|
});
|
|
55
57
|
continue;
|
|
@@ -63,9 +65,10 @@ export async function parseSlicePlan(filePath) {
|
|
|
63
65
|
entries.push({
|
|
64
66
|
index: unindexedCounter,
|
|
65
67
|
name: unindexed[3].trim(),
|
|
66
|
-
status: isChecked ?
|
|
68
|
+
status: isChecked ? STATUS.Complete : STATUS.NotStarted,
|
|
67
69
|
isChecked,
|
|
68
70
|
lineIndex: i,
|
|
71
|
+
indexSource: 'fallback',
|
|
69
72
|
...(description && { description }),
|
|
70
73
|
});
|
|
71
74
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slicePlanParser.js","sourceRoot":"","sources":["../../../src/introspection/parsers/slicePlanParser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"slicePlanParser.js","sourceRoot":"","sources":["../../../src/introspection/parsers/slicePlanParser.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import type { NormalizedStatus } from '../types.js';
|
|
2
|
-
/**
|
|
3
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Normalize a raw status string to one of the canonical values.
|
|
4
|
+
*
|
|
5
|
+
* Returns `undefined` when the input was present but not recognized —
|
|
6
|
+
* this is distinct from a real `not-started` and must not be conflated
|
|
7
|
+
* with it. `deferred` is intentionally absent from `STATUS_MAP` (it has
|
|
8
|
+
* no `NormalizedStatus` equivalent) and therefore also normalizes to
|
|
9
|
+
* `undefined`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function normalizeStatus(raw: string | undefined | null): NormalizedStatus | undefined;
|
|
4
12
|
//# sourceMappingURL=statusNormalizer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"statusNormalizer.d.ts","sourceRoot":"","sources":["../../../src/introspection/parsers/statusNormalizer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"statusNormalizer.d.ts","sourceRoot":"","sources":["../../../src/introspection/parsers/statusNormalizer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAoBpD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,gBAAgB,GAAG,SAAS,CAG5F"}
|
|
@@ -1,23 +1,32 @@
|
|
|
1
|
+
import { STATUS } from '../types.js';
|
|
1
2
|
/** Maps variant status spellings to normalized values (ported from parse.py _STATUS) */
|
|
2
3
|
const STATUS_MAP = {
|
|
3
|
-
complete:
|
|
4
|
-
completed:
|
|
5
|
-
done:
|
|
6
|
-
in_progress:
|
|
7
|
-
'in-progress':
|
|
8
|
-
'in progress':
|
|
9
|
-
active:
|
|
10
|
-
not_started:
|
|
11
|
-
'not-started':
|
|
12
|
-
'not started':
|
|
13
|
-
ready:
|
|
14
|
-
pending:
|
|
15
|
-
planned:
|
|
16
|
-
deprecated:
|
|
4
|
+
complete: STATUS.Complete,
|
|
5
|
+
completed: STATUS.Complete,
|
|
6
|
+
done: STATUS.Complete,
|
|
7
|
+
in_progress: STATUS.InProgress,
|
|
8
|
+
'in-progress': STATUS.InProgress,
|
|
9
|
+
'in progress': STATUS.InProgress,
|
|
10
|
+
active: STATUS.InProgress,
|
|
11
|
+
not_started: STATUS.NotStarted,
|
|
12
|
+
'not-started': STATUS.NotStarted,
|
|
13
|
+
'not started': STATUS.NotStarted,
|
|
14
|
+
ready: STATUS.NotStarted,
|
|
15
|
+
pending: STATUS.NotStarted,
|
|
16
|
+
planned: STATUS.NotStarted,
|
|
17
|
+
deprecated: STATUS.Deprecated,
|
|
17
18
|
};
|
|
18
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* Normalize a raw status string to one of the canonical values.
|
|
21
|
+
*
|
|
22
|
+
* Returns `undefined` when the input was present but not recognized —
|
|
23
|
+
* this is distinct from a real `not-started` and must not be conflated
|
|
24
|
+
* with it. `deferred` is intentionally absent from `STATUS_MAP` (it has
|
|
25
|
+
* no `NormalizedStatus` equivalent) and therefore also normalizes to
|
|
26
|
+
* `undefined`.
|
|
27
|
+
*/
|
|
19
28
|
export function normalizeStatus(raw) {
|
|
20
29
|
const key = (raw ?? '').toLowerCase().trim();
|
|
21
|
-
return STATUS_MAP[key]
|
|
30
|
+
return STATUS_MAP[key];
|
|
22
31
|
}
|
|
23
32
|
//# sourceMappingURL=statusNormalizer.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"statusNormalizer.js","sourceRoot":"","sources":["../../../src/introspection/parsers/statusNormalizer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"statusNormalizer.js","sourceRoot":"","sources":["../../../src/introspection/parsers/statusNormalizer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,wFAAwF;AACxF,MAAM,UAAU,GAAqC;IACnD,QAAQ,EAAE,MAAM,CAAC,QAAQ;IACzB,SAAS,EAAE,MAAM,CAAC,QAAQ;IAC1B,IAAI,EAAE,MAAM,CAAC,QAAQ;IACrB,WAAW,EAAE,MAAM,CAAC,UAAU;IAC9B,aAAa,EAAE,MAAM,CAAC,UAAU;IAChC,aAAa,EAAE,MAAM,CAAC,UAAU;IAChC,MAAM,EAAE,MAAM,CAAC,UAAU;IACzB,WAAW,EAAE,MAAM,CAAC,UAAU;IAC9B,aAAa,EAAE,MAAM,CAAC,UAAU;IAChC,aAAa,EAAE,MAAM,CAAC,UAAU;IAChC,KAAK,EAAE,MAAM,CAAC,UAAU;IACxB,OAAO,EAAE,MAAM,CAAC,UAAU;IAC1B,OAAO,EAAE,MAAM,CAAC,UAAU;IAC1B,UAAU,EAAE,MAAM,CAAC,UAAU;CAC9B,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAAC,GAA8B;IAC5D,MAAM,GAAG,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;IAC7C,OAAO,UAAU,CAAC,GAAG,CAAC,CAAC;AACzB,CAAC"}
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import type { TaskItem, TaskFileResult } from '../types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Extract checkbox items from a single task file.
|
|
4
|
-
*
|
|
4
|
+
* A missing file (ENOENT) is a normal "no task file" signal and returns an
|
|
5
|
+
* empty array. Any other read failure (permission denied, EISDIR, etc.) is a
|
|
6
|
+
* genuine resolution failure and propagates — callers must not conflate it
|
|
7
|
+
* with "no task file" (TD-2a: a resolution failure is never coerced to absent).
|
|
5
8
|
*/
|
|
6
9
|
export declare function parseTaskItems(filePath: string): Promise<TaskItem[]>;
|
|
7
10
|
/**
|
|
8
11
|
* Parse one or more task files, merge items, compute counts and inferred status.
|
|
9
12
|
* For multiple files (split support): merge items in path order, use first file's path.
|
|
10
|
-
*
|
|
13
|
+
* Propagates a genuine read failure from parseTaskItems (see above); only a
|
|
14
|
+
* missing file is treated as "no tasks".
|
|
11
15
|
*/
|
|
12
16
|
export declare function parseTaskFile(filePaths: string | string[]): Promise<TaskFileResult>;
|
|
13
17
|
//# sourceMappingURL=taskFileParser.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"taskFileParser.d.ts","sourceRoot":"","sources":["../../../src/introspection/parsers/taskFileParser.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"taskFileParser.d.ts","sourceRoot":"","sources":["../../../src/introspection/parsers/taskFileParser.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAQ5D;;;;;;GAMG;AACH,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAsB1E;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC,CA+BzF"}
|
|
@@ -1,36 +1,45 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { STATUS } from '../types.js';
|
|
2
3
|
/** Matches `- [ ] text` and `- [x] text` at any indentation level (from parse.py) */
|
|
3
4
|
const CHECKBOX_RE = /^(?:\s*)-\s+\[([ xX])\]\s+(.+)$/;
|
|
4
5
|
/** Maximum task name length before truncation (matching parse.py) */
|
|
5
6
|
const MAX_NAME_LENGTH = 120;
|
|
6
7
|
/**
|
|
7
8
|
* Extract checkbox items from a single task file.
|
|
8
|
-
*
|
|
9
|
+
* A missing file (ENOENT) is a normal "no task file" signal and returns an
|
|
10
|
+
* empty array. Any other read failure (permission denied, EISDIR, etc.) is a
|
|
11
|
+
* genuine resolution failure and propagates — callers must not conflate it
|
|
12
|
+
* with "no task file" (TD-2a: a resolution failure is never coerced to absent).
|
|
9
13
|
*/
|
|
10
14
|
export async function parseTaskItems(filePath) {
|
|
11
15
|
const items = [];
|
|
16
|
+
let content;
|
|
12
17
|
try {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (name.length > MAX_NAME_LENGTH) {
|
|
19
|
-
name = name.slice(0, MAX_NAME_LENGTH - 3) + '...';
|
|
20
|
-
}
|
|
21
|
-
items.push({ name, done: m[1].toLowerCase() === 'x' });
|
|
22
|
-
}
|
|
18
|
+
content = await readFile(filePath, 'utf-8');
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
if (err instanceof Error && 'code' in err && err.code === 'ENOENT') {
|
|
22
|
+
return items;
|
|
23
23
|
}
|
|
24
|
+
throw err;
|
|
24
25
|
}
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
for (const line of content.split('\n')) {
|
|
27
|
+
const m = CHECKBOX_RE.exec(line);
|
|
28
|
+
if (m) {
|
|
29
|
+
let name = m[2].trim();
|
|
30
|
+
if (name.length > MAX_NAME_LENGTH) {
|
|
31
|
+
name = name.slice(0, MAX_NAME_LENGTH - 3) + '...';
|
|
32
|
+
}
|
|
33
|
+
items.push({ name, done: m[1].toLowerCase() === 'x' });
|
|
34
|
+
}
|
|
27
35
|
}
|
|
28
36
|
return items;
|
|
29
37
|
}
|
|
30
38
|
/**
|
|
31
39
|
* Parse one or more task files, merge items, compute counts and inferred status.
|
|
32
40
|
* For multiple files (split support): merge items in path order, use first file's path.
|
|
33
|
-
*
|
|
41
|
+
* Propagates a genuine read failure from parseTaskItems (see above); only a
|
|
42
|
+
* missing file is treated as "no tasks".
|
|
34
43
|
*/
|
|
35
44
|
export async function parseTaskFile(filePaths) {
|
|
36
45
|
const paths = Array.isArray(filePaths) ? filePaths : [filePaths];
|
|
@@ -44,16 +53,16 @@ export async function parseTaskFile(filePaths) {
|
|
|
44
53
|
const completedTasks = allItems.filter((item) => item.done).length;
|
|
45
54
|
let inferredStatus;
|
|
46
55
|
if (totalTasks === 0) {
|
|
47
|
-
inferredStatus =
|
|
56
|
+
inferredStatus = STATUS.NotStarted;
|
|
48
57
|
}
|
|
49
58
|
else if (completedTasks === totalTasks) {
|
|
50
|
-
inferredStatus =
|
|
59
|
+
inferredStatus = STATUS.Complete;
|
|
51
60
|
}
|
|
52
61
|
else if (completedTasks > 0) {
|
|
53
|
-
inferredStatus =
|
|
62
|
+
inferredStatus = STATUS.InProgress;
|
|
54
63
|
}
|
|
55
64
|
else {
|
|
56
|
-
inferredStatus =
|
|
65
|
+
inferredStatus = STATUS.NotStarted;
|
|
57
66
|
}
|
|
58
67
|
return {
|
|
59
68
|
filePath: primaryPath,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"taskFileParser.js","sourceRoot":"","sources":["../../../src/introspection/parsers/taskFileParser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"taskFileParser.js","sourceRoot":"","sources":["../../../src/introspection/parsers/taskFileParser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,qFAAqF;AACrF,MAAM,WAAW,GAAG,iCAAiC,CAAC;AAEtD,qEAAqE;AACrE,MAAM,eAAe,GAAG,GAAG,CAAC;AAE5B;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,QAAgB;IACnD,MAAM,KAAK,GAAe,EAAE,CAAC;IAC7B,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC9C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,KAAK,IAAI,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACnE,OAAO,KAAK,CAAC;QACf,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,MAAM,CAAC,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,CAAC,EAAE,CAAC;YACN,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YACvB,IAAI,IAAI,CAAC,MAAM,GAAG,eAAe,EAAE,CAAC;gBAClC,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,eAAe,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC;YACpD,CAAC;YACD,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,GAAG,EAAE,CAAC,CAAC;QACzD,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,SAA4B;IAC9D,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IACjE,MAAM,WAAW,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAEnC,MAAM,QAAQ,GAAe,EAAE,CAAC;IAChC,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,CAAC,CAAC,CAAC;QACtC,QAAQ,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC;IAC1B,CAAC;IAED,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC;IACnC,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;IAEnE,IAAI,cAAgD,CAAC;IACrD,IAAI,UAAU,KAAK,CAAC,EAAE,CAAC;QACrB,cAAc,GAAG,MAAM,CAAC,UAAU,CAAC;IACrC,CAAC;SAAM,IAAI,cAAc,KAAK,UAAU,EAAE,CAAC;QACzC,cAAc,GAAG,MAAM,CAAC,QAAQ,CAAC;IACnC,CAAC;SAAM,IAAI,cAAc,GAAG,CAAC,EAAE,CAAC;QAC9B,cAAc,GAAG,MAAM,CAAC,UAAU,CAAC;IACrC,CAAC;SAAM,CAAC;QACN,cAAc,GAAG,MAAM,CAAC,UAAU,CAAC;IACrC,CAAC;IAED,OAAO;QACL,QAAQ,EAAE,WAAW;QACrB,KAAK,EAAE,QAAQ;QACf,UAAU;QACV,cAAc;QACd,cAAc;KACf,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { ConfigManager } from '../config/ConfigManager.js';
|
|
2
|
+
/** Frontmatter verdict vocabulary (uppercase, untrusted external data) */
|
|
3
|
+
export type Verdict = 'PASS' | 'CONCERNS' | 'FAIL' | 'UNKNOWN';
|
|
4
|
+
/** Config token for the verdict floor that clears a gate (lowercase, project policy) */
|
|
5
|
+
export type ThresholdToken = 'pass' | 'concerns';
|
|
6
|
+
/** Config token for how to treat an UNKNOWN/absent/unparseable verdict */
|
|
7
|
+
export type UnknownPolicy = 'fail' | 'concerns' | 'pass';
|
|
8
|
+
/** Outcome of evaluating a present review's verdict against policy */
|
|
9
|
+
export type GateOutcome = 'clears' | 'pending' | 'failed';
|
|
10
|
+
/** A lifecycle boundary at which a review may be owed */
|
|
11
|
+
export type Boundary = 'preSlicePlan' | 'preTasks' | 'preImplementation' | 'preAdvance';
|
|
12
|
+
/** Maps a lifecycle boundary to the review type owed at that boundary. */
|
|
13
|
+
export declare function positionToReviewType(boundary: Boundary): string;
|
|
14
|
+
/**
|
|
15
|
+
* Normalizes a raw frontmatter verdict to the known vocabulary.
|
|
16
|
+
* Untrusted external data: anything absent or unrecognized degrades to UNKNOWN, never throws.
|
|
17
|
+
*/
|
|
18
|
+
export declare function normalizeVerdict(raw: string | undefined): Verdict;
|
|
19
|
+
/**
|
|
20
|
+
* The verdict decision matrix (design TD-2). Returns only 'clears'/'failed' — the
|
|
21
|
+
* 'pending' outcome belongs to the caller, derived from the absent-artifact signal.
|
|
22
|
+
*/
|
|
23
|
+
export declare function evaluateVerdict(verdict: Verdict, threshold: ThresholdToken, unknownAs: UnknownPolicy): GateOutcome;
|
|
24
|
+
/** Resolved gate policy, ready for the navigator to evaluate any boundary. */
|
|
25
|
+
export interface ResolvedGate {
|
|
26
|
+
threshold: ThresholdToken;
|
|
27
|
+
unknownAs: UnknownPolicy;
|
|
28
|
+
thresholdFor(boundary: Boundary): ThresholdToken;
|
|
29
|
+
/** YYYYMMDD cutoff, or '' for no cutoff. Grandfathers artifacts dated earlier out of every boundary. */
|
|
30
|
+
effectiveDate: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Reads gate config and resolves policy. Returns null when gating is off (caller skips
|
|
34
|
+
* — no artifact lookup). A missing config file is not a failure (ConfigManager already
|
|
35
|
+
* falls through to the built-in default, i.e. gating off). A genuine read/parse failure
|
|
36
|
+
* propagates (TD-8a). An out-of-vocabulary token throws a descriptive error (TD-8b) —
|
|
37
|
+
* config is the project's own declared policy, so it fails fast rather than degrading
|
|
38
|
+
* to UNKNOWN the way an untrusted frontmatter verdict does.
|
|
39
|
+
*/
|
|
40
|
+
export declare function resolveGateConfig(config: ConfigManager): Promise<ResolvedGate | null>;
|
|
41
|
+
/** Gated status a boundary evaluation can produce, or null to keep the caller's existing status. */
|
|
42
|
+
type GateStatus = 'pending-review' | 'review-failed';
|
|
43
|
+
/** Result of evaluating a gate at a boundary: the status plus a human-readable rationale. */
|
|
44
|
+
export interface GateEvaluation {
|
|
45
|
+
status: GateStatus;
|
|
46
|
+
reviewType: string;
|
|
47
|
+
rationale: string;
|
|
48
|
+
/** Relative path (from projectPath) to the review artifact, when one was found.
|
|
49
|
+
* Absent for pending-review (no artifact exists yet). Lets callers point a
|
|
50
|
+
* finding's location at the artifact without a second detectDocuments call. */
|
|
51
|
+
artifactPath?: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Composite gate evaluation for one boundary of one slice/arch index.
|
|
55
|
+
* Returns null when gating is off OR the review clears — i.e. "nothing to flag."
|
|
56
|
+
* Returns a GateEvaluation when the review is absent (pending-review) or
|
|
57
|
+
* present-but-not-clearing (review-failed). Pure I/O over the pure helpers;
|
|
58
|
+
* both WorkflowNavigator and ConsistencyChecker call this.
|
|
59
|
+
*/
|
|
60
|
+
export declare function evaluateReviewGate(projectPath: string, index: number, boundary: Boundary, config: ConfigManager, resolved?: ResolvedGate): Promise<GateEvaluation | null>;
|
|
61
|
+
export {};
|
|
62
|
+
//# sourceMappingURL=reviewGate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reviewGate.d.ts","sourceRoot":"","sources":["../../src/introspection/reviewGate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAIhE,0EAA0E;AAC1E,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG,UAAU,GAAG,MAAM,GAAG,SAAS,CAAC;AAE/D,wFAAwF;AACxF,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,UAAU,CAAC;AAEjD,0EAA0E;AAC1E,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC;AAEzD,sEAAsE;AACtE,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE1D,yDAAyD;AACzD,MAAM,MAAM,QAAQ,GAAG,cAAc,GAAG,UAAU,GAAG,mBAAmB,GAAG,YAAY,CAAC;AA0BxF,0EAA0E;AAC1E,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAE/D;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAIjE;AAoBD;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,cAAc,EACzB,SAAS,EAAE,aAAa,GACvB,WAAW,CAab;AAED,8EAA8E;AAC9E,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,cAAc,CAAC;IAC1B,SAAS,EAAE,aAAa,CAAC;IACzB,YAAY,CAAC,QAAQ,EAAE,QAAQ,GAAG,cAAc,CAAC;IACjD,wGAAwG;IACxG,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAmC3F;AAED,oGAAoG;AACpG,KAAK,UAAU,GAAG,gBAAgB,GAAG,eAAe,CAAC;AAErD,6FAA6F;AAC7F,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,UAAU,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB;;oFAEgF;IAChF,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACtC,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,aAAa,EACrB,QAAQ,CAAC,EAAE,YAAY,GACtB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAwDhC"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { detectDocuments } from './parsers/documentDetector.js';
|
|
3
|
+
import { parseFrontmatter } from './parsers/frontmatterParser.js';
|
|
4
|
+
/** Boundary → review type. Single source of truth: position derives the type, never configured. */
|
|
5
|
+
const BOUNDARY_REVIEW_TYPE = {
|
|
6
|
+
preSlicePlan: 'arch',
|
|
7
|
+
preTasks: 'slice',
|
|
8
|
+
preImplementation: 'tasks',
|
|
9
|
+
preAdvance: 'code',
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Per-gate threshold override config key, keyed by boundary. Mirrors ConfigKeys.ts.
|
|
13
|
+
* Key segments match BOUNDARY_REVIEW_TYPE's values (arch/slice/tasks/code), not the
|
|
14
|
+
* boundary names, for consistency with the review-type vocabulary used elsewhere.
|
|
15
|
+
*/
|
|
16
|
+
const BOUNDARY_THRESHOLD_KEY = {
|
|
17
|
+
preSlicePlan: 'workflow.review_gates.arch.threshold',
|
|
18
|
+
preTasks: 'workflow.review_gates.slice.threshold',
|
|
19
|
+
preImplementation: 'workflow.review_gates.tasks.threshold',
|
|
20
|
+
preAdvance: 'workflow.review_gates.code.threshold',
|
|
21
|
+
};
|
|
22
|
+
const KNOWN_VERDICTS = ['PASS', 'CONCERNS', 'FAIL', 'UNKNOWN'];
|
|
23
|
+
const KNOWN_THRESHOLDS = ['pass', 'concerns'];
|
|
24
|
+
const KNOWN_UNKNOWN_POLICIES = ['fail', 'concerns', 'pass'];
|
|
25
|
+
/** Maps a lifecycle boundary to the review type owed at that boundary. */
|
|
26
|
+
export function positionToReviewType(boundary) {
|
|
27
|
+
return BOUNDARY_REVIEW_TYPE[boundary];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Normalizes a raw frontmatter verdict to the known vocabulary.
|
|
31
|
+
* Untrusted external data: anything absent or unrecognized degrades to UNKNOWN, never throws.
|
|
32
|
+
*/
|
|
33
|
+
export function normalizeVerdict(raw) {
|
|
34
|
+
if (raw === undefined)
|
|
35
|
+
return 'UNKNOWN';
|
|
36
|
+
const upper = raw.trim().toUpperCase();
|
|
37
|
+
return KNOWN_VERDICTS.includes(upper) ? upper : 'UNKNOWN';
|
|
38
|
+
}
|
|
39
|
+
function parseThresholdToken(raw, key) {
|
|
40
|
+
if (KNOWN_THRESHOLDS.includes(raw)) {
|
|
41
|
+
return raw;
|
|
42
|
+
}
|
|
43
|
+
throw new Error(`Config key "${key}" must be one of [${KNOWN_THRESHOLDS.map((v) => `"${v}"`).join(', ')}], got "${raw}"`);
|
|
44
|
+
}
|
|
45
|
+
function parseUnknownPolicy(raw, key) {
|
|
46
|
+
if (KNOWN_UNKNOWN_POLICIES.includes(raw)) {
|
|
47
|
+
return raw;
|
|
48
|
+
}
|
|
49
|
+
throw new Error(`Config key "${key}" must be one of [${KNOWN_UNKNOWN_POLICIES.map((v) => `"${v}"`).join(', ')}], got "${raw}"`);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The verdict decision matrix (design TD-2). Returns only 'clears'/'failed' — the
|
|
53
|
+
* 'pending' outcome belongs to the caller, derived from the absent-artifact signal.
|
|
54
|
+
*/
|
|
55
|
+
export function evaluateVerdict(verdict, threshold, unknownAs) {
|
|
56
|
+
switch (verdict) {
|
|
57
|
+
case 'PASS':
|
|
58
|
+
return 'clears';
|
|
59
|
+
case 'FAIL':
|
|
60
|
+
return 'failed';
|
|
61
|
+
case 'CONCERNS':
|
|
62
|
+
return threshold === 'concerns' ? 'clears' : 'failed';
|
|
63
|
+
case 'UNKNOWN': {
|
|
64
|
+
const standIn = unknownAs === 'fail' ? 'FAIL' : unknownAs === 'concerns' ? 'CONCERNS' : 'PASS';
|
|
65
|
+
return evaluateVerdict(standIn, threshold, unknownAs);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Reads gate config and resolves policy. Returns null when gating is off (caller skips
|
|
71
|
+
* — no artifact lookup). A missing config file is not a failure (ConfigManager already
|
|
72
|
+
* falls through to the built-in default, i.e. gating off). A genuine read/parse failure
|
|
73
|
+
* propagates (TD-8a). An out-of-vocabulary token throws a descriptive error (TD-8b) —
|
|
74
|
+
* config is the project's own declared policy, so it fails fast rather than degrading
|
|
75
|
+
* to UNKNOWN the way an untrusted frontmatter verdict does.
|
|
76
|
+
*/
|
|
77
|
+
export async function resolveGateConfig(config) {
|
|
78
|
+
const enabled = await config.get('workflow.review_enabled');
|
|
79
|
+
if (enabled.value !== true) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
const globalThresholdRaw = await config.get('workflow.review_threshold');
|
|
83
|
+
const unknownAsRaw = await config.get('workflow.review_unknown_as');
|
|
84
|
+
const globalThreshold = parseThresholdToken(String(globalThresholdRaw.value), 'workflow.review_threshold');
|
|
85
|
+
const unknownAs = parseUnknownPolicy(String(unknownAsRaw.value), 'workflow.review_unknown_as');
|
|
86
|
+
const overrides = new Map();
|
|
87
|
+
for (const boundary of Object.keys(BOUNDARY_THRESHOLD_KEY)) {
|
|
88
|
+
const key = BOUNDARY_THRESHOLD_KEY[boundary];
|
|
89
|
+
const overrideRaw = await config.get(key);
|
|
90
|
+
const overrideValue = String(overrideRaw.value);
|
|
91
|
+
if (overrideValue !== '') {
|
|
92
|
+
overrides.set(boundary, parseThresholdToken(overrideValue, key));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const effectiveDateRaw = await config.get('workflow.review_gate_effective_date');
|
|
96
|
+
const effectiveDate = String(effectiveDateRaw.value);
|
|
97
|
+
return {
|
|
98
|
+
threshold: globalThreshold,
|
|
99
|
+
unknownAs,
|
|
100
|
+
effectiveDate,
|
|
101
|
+
thresholdFor(boundary) {
|
|
102
|
+
return overrides.get(boundary) ?? globalThreshold;
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Composite gate evaluation for one boundary of one slice/arch index.
|
|
108
|
+
* Returns null when gating is off OR the review clears — i.e. "nothing to flag."
|
|
109
|
+
* Returns a GateEvaluation when the review is absent (pending-review) or
|
|
110
|
+
* present-but-not-clearing (review-failed). Pure I/O over the pure helpers;
|
|
111
|
+
* both WorkflowNavigator and ConsistencyChecker call this.
|
|
112
|
+
*/
|
|
113
|
+
export async function evaluateReviewGate(projectPath, index, boundary, config, resolved) {
|
|
114
|
+
const gate = resolved ?? (await resolveGateConfig(config));
|
|
115
|
+
if (gate === null)
|
|
116
|
+
return null;
|
|
117
|
+
const reviewType = positionToReviewType(boundary);
|
|
118
|
+
const docs = await detectDocuments(projectPath, index, reviewType);
|
|
119
|
+
// preSlicePlan gates an architecture index; every other boundary gates a
|
|
120
|
+
// slice index — read whichever artifact's frontmatter is relevant to this
|
|
121
|
+
// boundary, once, shared by both checks below.
|
|
122
|
+
const gatedArtifactPath = boundary === 'preSlicePlan' ? docs.architecture : docs.sliceDesign;
|
|
123
|
+
const gatedArtifactFrontmatter = gatedArtifactPath
|
|
124
|
+
? await parseFrontmatter(join(projectPath, gatedArtifactPath))
|
|
125
|
+
: null;
|
|
126
|
+
// Effective-date grandfather cutoff: a slice/architecture designed before this
|
|
127
|
+
// date is exempt from every gate boundary, uniformly, so turning on gating on
|
|
128
|
+
// a project with existing history doesn't retroactively demand reviews for
|
|
129
|
+
// work that predates the gate. Checked before the docs-only declaration below
|
|
130
|
+
// since a grandfathered slice needs no declaration at all.
|
|
131
|
+
if (gate.effectiveDate !== '' && gatedArtifactFrontmatter) {
|
|
132
|
+
const dateCreated = gatedArtifactFrontmatter.data.dateCreated;
|
|
133
|
+
if (dateCreated && dateCreated < gate.effectiveDate) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// Docs-only declaration (#57): a slice-design frontmatter of codeReview: none
|
|
138
|
+
// clears the pre-advance ('code') gate unconditionally — this slice cannot
|
|
139
|
+
// produce a code review. Absent (the default) leaves the gate unaffected.
|
|
140
|
+
// Scoped to preAdvance only; every other boundary evaluates normally.
|
|
141
|
+
if (boundary === 'preAdvance' && gatedArtifactFrontmatter?.data.codeReview === 'none') {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
if (docs.review === null) {
|
|
145
|
+
return {
|
|
146
|
+
status: 'pending-review',
|
|
147
|
+
reviewType,
|
|
148
|
+
rationale: `Slice ${index} requires a ${reviewType} review before proceeding — no review artifact found.`,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
const frontmatter = await parseFrontmatter(join(projectPath, docs.review));
|
|
152
|
+
const verdict = normalizeVerdict(frontmatter.data.verdict);
|
|
153
|
+
const threshold = gate.thresholdFor(boundary);
|
|
154
|
+
const outcome = evaluateVerdict(verdict, threshold, gate.unknownAs);
|
|
155
|
+
if (outcome === 'clears')
|
|
156
|
+
return null;
|
|
157
|
+
return {
|
|
158
|
+
status: 'review-failed',
|
|
159
|
+
reviewType,
|
|
160
|
+
rationale: `Review artifact present but verdict ${verdict} does not clear threshold '${threshold}' for slice ${index}.`,
|
|
161
|
+
artifactPath: docs.review,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=reviewGate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reviewGate.js","sourceRoot":"","sources":["../../src/introspection/reviewGate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type NormalizedStatus } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Already-resolved signals for a single slice-plan entry. The caller is
|
|
4
|
+
* responsible for I/O (detectDocuments / parseTaskFile / parseFrontmatter)
|
|
5
|
+
* and for distinguishing "signal absent" (leave the field undefined) from
|
|
6
|
+
* "signal present but failed to resolve" (do not call this function with a
|
|
7
|
+
* placeholder — surface the resolution failure instead; see TD-2a).
|
|
8
|
+
*/
|
|
9
|
+
export interface EntryStatusSignals {
|
|
10
|
+
/** Slice-design frontmatter status, if a design exists and normalized cleanly. */
|
|
11
|
+
frontmatterStatus?: NormalizedStatus;
|
|
12
|
+
/** Task file's inferred status, if a task file exists and parsed cleanly. */
|
|
13
|
+
taskInferredStatus?: NormalizedStatus;
|
|
14
|
+
/** The slice-plan checkbox. Its only authority is asserting complete. */
|
|
15
|
+
isChecked: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Derive a slice-plan entry's status from the precedence lattice
|
|
19
|
+
* (highest priority first): deprecated frontmatter > computed task
|
|
20
|
+
* completion > slice-design frontmatter > plan checkbox.
|
|
21
|
+
*/
|
|
22
|
+
export declare function deriveEntryStatus(signals: EntryStatusSignals): NormalizedStatus;
|
|
23
|
+
//# sourceMappingURL=statusDerivation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"statusDerivation.d.ts","sourceRoot":"","sources":["../../src/introspection/statusDerivation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE3D;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC,kFAAkF;IAClF,iBAAiB,CAAC,EAAE,gBAAgB,CAAC;IACrC,6EAA6E;IAC7E,kBAAkB,CAAC,EAAE,gBAAgB,CAAC;IACtC,yEAAyE;IACzE,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,GAAG,gBAAgB,CAW/E"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { STATUS } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Derive a slice-plan entry's status from the precedence lattice
|
|
4
|
+
* (highest priority first): deprecated frontmatter > computed task
|
|
5
|
+
* completion > slice-design frontmatter > plan checkbox.
|
|
6
|
+
*/
|
|
7
|
+
export function deriveEntryStatus(signals) {
|
|
8
|
+
if (signals.frontmatterStatus === STATUS.Deprecated) {
|
|
9
|
+
return STATUS.Deprecated;
|
|
10
|
+
}
|
|
11
|
+
if (signals.taskInferredStatus !== undefined) {
|
|
12
|
+
return signals.taskInferredStatus;
|
|
13
|
+
}
|
|
14
|
+
if (signals.frontmatterStatus !== undefined) {
|
|
15
|
+
return signals.frontmatterStatus;
|
|
16
|
+
}
|
|
17
|
+
return signals.isChecked ? STATUS.Complete : STATUS.NotStarted;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=statusDerivation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"statusDerivation.js","sourceRoot":"","sources":["../../src/introspection/statusDerivation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAyB,MAAM,YAAY,CAAC;AAkB3D;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAA2B;IAC3D,IAAI,OAAO,CAAC,iBAAiB,KAAK,MAAM,CAAC,UAAU,EAAE,CAAC;QACpD,OAAO,MAAM,CAAC,UAAU,CAAC;IAC3B,CAAC;IACD,IAAI,OAAO,CAAC,kBAAkB,KAAK,SAAS,EAAE,CAAC;QAC7C,OAAO,OAAO,CAAC,kBAAkB,CAAC;IACpC,CAAC;IACD,IAAI,OAAO,CAAC,iBAAiB,KAAK,SAAS,EAAE,CAAC;QAC5C,OAAO,OAAO,CAAC,iBAAiB,CAAC;IACnC,CAAC;IACD,OAAO,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC;AACjE,CAAC"}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
/** Normalized status values used across introspection results */
|
|
2
|
-
export
|
|
2
|
+
export declare const STATUS: {
|
|
3
|
+
readonly Complete: "complete";
|
|
4
|
+
readonly InProgress: "in-progress";
|
|
5
|
+
readonly NotStarted: "not-started";
|
|
6
|
+
readonly Deprecated: "deprecated";
|
|
7
|
+
};
|
|
8
|
+
export type NormalizedStatus = (typeof STATUS)[keyof typeof STATUS];
|
|
3
9
|
/** Result of parsing a single slice plan entry */
|
|
4
10
|
export interface SlicePlanEntry {
|
|
5
11
|
index: number;
|
|
@@ -10,6 +16,13 @@ export interface SlicePlanEntry {
|
|
|
10
16
|
lineIndex: number;
|
|
11
17
|
/** Overview/description text from after the bold name (e.g., " — Summary text...") */
|
|
12
18
|
description?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Whether `index` came from a bolded `(NNN)` in the source ('explicit') or was
|
|
21
|
+
* synthesized as a per-file sequential counter for unindexed entries ('fallback').
|
|
22
|
+
* Fallback indices are not stable across files and must not enter cross-plan
|
|
23
|
+
* aggregation (see ConsistencyChecker.checkAll()).
|
|
24
|
+
*/
|
|
25
|
+
indexSource: 'explicit' | 'fallback';
|
|
13
26
|
}
|
|
14
27
|
/** Result of parsing a slice plan document */
|
|
15
28
|
export interface SlicePlanResult {
|
|
@@ -30,7 +43,7 @@ export interface TaskFileResult {
|
|
|
30
43
|
totalTasks: number;
|
|
31
44
|
completedTasks: number;
|
|
32
45
|
/** Inferred status based on checkbox state */
|
|
33
|
-
inferredStatus:
|
|
46
|
+
inferredStatus: NormalizedStatus;
|
|
34
47
|
}
|
|
35
48
|
/** Extracted YAML frontmatter fields */
|
|
36
49
|
export interface FrontmatterData {
|
|
@@ -61,6 +74,7 @@ export interface DocumentDetectionResult {
|
|
|
61
74
|
taskFile: string[] | null;
|
|
62
75
|
architecture: string | null;
|
|
63
76
|
slicePlan: string | null;
|
|
77
|
+
review: string | null;
|
|
64
78
|
}
|
|
65
79
|
/** Base document shape produced by _d() in parse.py */
|
|
66
80
|
export interface DocSummary {
|
|
@@ -218,12 +232,17 @@ export interface ConsistencyFixResult extends ConsistencyCheckResult {
|
|
|
218
232
|
export interface SliceStatus {
|
|
219
233
|
name: string;
|
|
220
234
|
index: number | null;
|
|
221
|
-
status: 'needs-design' | 'needs-tasks' | 'in-implementation' | 'complete' | 'no-active-slice';
|
|
235
|
+
status: 'needs-design' | 'needs-tasks' | 'in-implementation' | 'complete' | 'no-active-slice' | 'pending-review' | 'review-failed';
|
|
222
236
|
taskProgress?: {
|
|
223
237
|
completed: number;
|
|
224
238
|
total: number;
|
|
225
239
|
inferredStatus: NormalizedStatus;
|
|
226
240
|
};
|
|
241
|
+
/** Set when status is 'pending-review' or 'review-failed'; carries the gate's rationale for getNext() to route without recomputing the gate. */
|
|
242
|
+
gateInfo?: {
|
|
243
|
+
reviewType: string;
|
|
244
|
+
rationale: string;
|
|
245
|
+
};
|
|
227
246
|
}
|
|
228
247
|
/** Full workflow status for a project */
|
|
229
248
|
export interface WorkflowStatus {
|