cc-devflow 4.5.2 → 4.5.4
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/.claude/skills/cc-act/CHANGELOG.md +19 -0
- package/.claude/skills/cc-act/PLAYBOOK.md +14 -1
- package/.claude/skills/cc-act/SKILL.md +46 -6
- package/.claude/skills/cc-act/assets/PR_BRIEF_TEMPLATE.md +44 -1
- package/.claude/skills/cc-act/assets/RELEASE_NOTE_TEMPLATE.md +18 -1
- package/.claude/skills/cc-act/references/closure-contract.md +3 -0
- package/.claude/skills/cc-act/scripts/cc-act-common.sh +27 -1
- package/.claude/skills/cc-act/scripts/render-pr-brief.sh +31 -0
- package/.claude/skills/cc-act/scripts/verify-act-gate.sh +6 -0
- package/.claude/skills/cc-check/CHANGELOG.md +18 -0
- package/.claude/skills/cc-check/PLAYBOOK.md +38 -7
- package/.claude/skills/cc-check/SKILL.md +39 -7
- package/.claude/skills/cc-check/assets/REPORT_CARD_TEMPLATE.json +61 -0
- package/.claude/skills/cc-check/references/gate-contract.md +11 -0
- package/.claude/skills/cc-check/references/review-contract.md +17 -1
- package/.claude/skills/cc-check/scripts/render-report-card.js +37 -0
- package/.claude/skills/cc-check/scripts/verify-gate.sh +7 -0
- package/.claude/skills/cc-do/CHANGELOG.md +18 -0
- package/.claude/skills/cc-do/PLAYBOOK.md +20 -13
- package/.claude/skills/cc-do/SKILL.md +37 -17
- package/.claude/skills/cc-do/references/execution-recovery.md +19 -5
- package/.claude/skills/cc-do/references/parallel-dispatch.md +6 -4
- package/.claude/skills/cc-do/scripts/detect-file-conflicts.sh +49 -3
- package/.claude/skills/cc-do/scripts/verify-task-gates.sh +19 -6
- package/.claude/skills/cc-do/scripts/write-task-checkpoint.sh +14 -2
- package/.claude/skills/cc-investigate/CHANGELOG.md +24 -0
- package/.claude/skills/cc-investigate/PLAYBOOK.md +35 -13
- package/.claude/skills/cc-investigate/SKILL.md +87 -20
- package/.claude/skills/cc-investigate/assets/ANALYSIS_TEMPLATE.md +68 -3
- package/.claude/skills/cc-investigate/assets/TASKS_TEMPLATE.md +9 -4
- package/.claude/skills/cc-investigate/assets/TASK_MANIFEST_TEMPLATE.json +41 -2
- package/.claude/skills/cc-investigate/references/investigation-contract.md +46 -0
- package/.claude/skills/cc-plan/CHANGELOG.md +32 -0
- package/.claude/skills/cc-plan/PLAYBOOK.md +26 -8
- package/.claude/skills/cc-plan/SKILL.md +79 -34
- package/.claude/skills/cc-plan/assets/DESIGN_TEMPLATE.md +71 -3
- package/.claude/skills/cc-plan/assets/TASKS_TEMPLATE.md +32 -0
- package/.claude/skills/cc-plan/assets/TASK_MANIFEST_TEMPLATE.json +76 -2
- package/.claude/skills/cc-plan/assets/TINY_DESIGN_TEMPLATE.md +58 -0
- package/.claude/skills/cc-plan/references/planning-contract.md +26 -4
- package/.claude/skills/cc-roadmap/CHANGELOG.md +14 -0
- package/.claude/skills/cc-roadmap/PLAYBOOK.md +10 -7
- package/.claude/skills/cc-roadmap/SKILL.md +43 -23
- package/.claude/skills/cc-roadmap/assets/BACKLOG_TEMPLATE.md +10 -0
- package/.claude/skills/cc-roadmap/assets/ROADMAP_TEMPLATE.md +15 -0
- package/.claude/skills/cc-roadmap/assets/TRACKING_TEMPLATE.json +1 -1
- package/.claude/skills/cc-roadmap/references/roadmap-dialogue.md +11 -7
- package/.claude/skills/cc-simplify/CHANGELOG.md +6 -0
- package/.claude/skills/cc-simplify/SKILL.md +10 -1
- package/.claude/skills/cc-spec-init/CHANGELOG.md +6 -0
- package/.claude/skills/cc-spec-init/SKILL.md +14 -1
- package/CHANGELOG.md +29 -0
- package/README.md +10 -2
- package/README.zh-CN.md +10 -2
- package/bin/cc-devflow-cli.js +93 -2
- package/docs/examples/example-bindings.json +7 -7
- package/docs/examples/full-design-blocked/BACKLOG.md +1 -1
- package/docs/examples/full-design-blocked/README.md +1 -1
- package/docs/examples/full-design-blocked/ROADMAP.md +1 -1
- package/docs/examples/full-design-blocked/changes/REQ-002-bulk-invite-import/planning/design.md +1 -1
- package/docs/examples/full-design-blocked/changes/REQ-002-bulk-invite-import/planning/tasks.md +1 -1
- package/docs/examples/full-design-blocked/roadmap-tracking.json +1 -1
- package/docs/examples/local-handoff/BACKLOG.md +1 -1
- package/docs/examples/local-handoff/README.md +1 -1
- package/docs/examples/local-handoff/ROADMAP.md +1 -1
- package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/planning/design.md +1 -1
- package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/planning/tasks.md +1 -1
- package/docs/examples/local-handoff/roadmap-tracking.json +1 -1
- package/docs/examples/pdca-loop/BACKLOG.md +1 -1
- package/docs/examples/pdca-loop/README.md +1 -1
- package/docs/examples/pdca-loop/ROADMAP.md +1 -1
- package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/planning/design.md +1 -1
- package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/planning/task-manifest.json +2 -2
- package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/planning/tasks.md +1 -1
- package/docs/examples/pdca-loop/roadmap-tracking.json +1 -1
- package/docs/get-shit-done-strategy-audit.md +518 -0
- package/docs/skill-strategy-audit.md +48 -0
- package/lib/compiler/__tests__/inventory.test.js +51 -0
- package/lib/compiler/inventory.js +78 -0
- package/lib/skill-runtime/__tests__/approve.test.js +92 -0
- package/lib/skill-runtime/__tests__/autopilot.test.js +4 -0
- package/lib/skill-runtime/__tests__/planner.tdd.test.js +20 -0
- package/lib/skill-runtime/__tests__/query.test.js +147 -1
- package/lib/skill-runtime/__tests__/readiness.test.js +53 -0
- package/lib/skill-runtime/__tests__/release.test.js +85 -0
- package/lib/skill-runtime/__tests__/runtime.integration.test.js +30 -1
- package/lib/skill-runtime/__tests__/schemas.test.js +56 -0
- package/lib/skill-runtime/__tests__/worker-run.test.js +29 -0
- package/lib/skill-runtime/errors.js +39 -0
- package/lib/skill-runtime/index.js +8 -0
- package/lib/skill-runtime/operations/approve.js +17 -2
- package/lib/skill-runtime/operations/release.js +6 -3
- package/lib/skill-runtime/operations/worker-run.js +30 -0
- package/lib/skill-runtime/planner.js +10 -2
- package/lib/skill-runtime/query-registry.js +101 -0
- package/lib/skill-runtime/query.js +159 -91
- package/lib/skill-runtime/readiness.js +84 -0
- package/lib/skill-runtime/schemas.js +39 -4
- package/lib/skill-runtime/trace.js +22 -0
- package/package.json +1 -1
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [INPUT]: 接收 runtime/query/compiler 边界抛出的错误或失败字段。
|
|
3
|
+
* [OUTPUT]: 生成可序列化 named error,保留 artifact refs 与 rescue action。
|
|
4
|
+
* [POS]: skill runtime 的失败语义层,避免用 null/false/string 表达可恢复失败。
|
|
5
|
+
* [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
class SkillRuntimeError extends Error {
|
|
9
|
+
constructor(name, message, options = {}) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = name;
|
|
12
|
+
this.artifactRefs = options.artifactRefs || [];
|
|
13
|
+
this.rescueAction = options.rescueAction || 'inspect-runtime-artifacts';
|
|
14
|
+
this.details = options.details || {};
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function namedError(name, message, options = {}) {
|
|
19
|
+
return new SkillRuntimeError(name, message, options);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function serializeError(error, fallbackName = 'SkillRuntimeError') {
|
|
23
|
+
const name = error?.name || fallbackName;
|
|
24
|
+
const message = error?.message || String(error || 'Unknown runtime error');
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
name,
|
|
28
|
+
message,
|
|
29
|
+
artifactRefs: error?.artifactRefs || [],
|
|
30
|
+
rescueAction: error?.rescueAction || 'inspect-runtime-artifacts',
|
|
31
|
+
details: error?.details || {}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = {
|
|
36
|
+
SkillRuntimeError,
|
|
37
|
+
namedError,
|
|
38
|
+
serializeError
|
|
39
|
+
};
|
|
@@ -9,6 +9,10 @@ const store = require('./store');
|
|
|
9
9
|
const schemas = require('./schemas');
|
|
10
10
|
const planner = require('./planner');
|
|
11
11
|
const query = require('./query');
|
|
12
|
+
const queryRegistry = require('./query-registry');
|
|
13
|
+
const errors = require('./errors');
|
|
14
|
+
const trace = require('./trace');
|
|
15
|
+
const readiness = require('./readiness');
|
|
12
16
|
const intent = require('./intent');
|
|
13
17
|
const artifacts = require('./artifacts');
|
|
14
18
|
const lifecycle = require('./lifecycle');
|
|
@@ -24,6 +28,10 @@ module.exports = {
|
|
|
24
28
|
...schemas,
|
|
25
29
|
...planner,
|
|
26
30
|
...query,
|
|
31
|
+
...queryRegistry,
|
|
32
|
+
...errors,
|
|
33
|
+
...trace,
|
|
34
|
+
...readiness,
|
|
27
35
|
...artifacts,
|
|
28
36
|
...intent,
|
|
29
37
|
...lifecycle,
|
|
@@ -15,6 +15,7 @@ const {
|
|
|
15
15
|
const { parseRuntimeState, parseManifest } = require('../schemas');
|
|
16
16
|
const { syncIntentMemory } = require('../intent');
|
|
17
17
|
const { normalizeExecutionMode } = require('../lifecycle');
|
|
18
|
+
const { namedError } = require('../errors');
|
|
18
19
|
|
|
19
20
|
async function runApprove({ repoRoot, changeId, executionMode }) {
|
|
20
21
|
const statePath = getRuntimeStatePath(repoRoot, changeId);
|
|
@@ -23,11 +24,25 @@ async function runApprove({ repoRoot, changeId, executionMode }) {
|
|
|
23
24
|
const rawManifest = await readJson(manifestPath, null);
|
|
24
25
|
|
|
25
26
|
if (!rawState) {
|
|
26
|
-
throw
|
|
27
|
+
throw namedError(
|
|
28
|
+
'MissingChangeStateError',
|
|
29
|
+
`Cannot approve ${changeId}: change-state.json is missing`,
|
|
30
|
+
{
|
|
31
|
+
artifactRefs: [statePath],
|
|
32
|
+
rescueAction: 'run cc-roadmap or cc-plan init before approving execution'
|
|
33
|
+
}
|
|
34
|
+
);
|
|
27
35
|
}
|
|
28
36
|
|
|
29
37
|
if (!rawManifest) {
|
|
30
|
-
throw
|
|
38
|
+
throw namedError(
|
|
39
|
+
'MissingTaskManifestError',
|
|
40
|
+
`Cannot approve ${changeId}: task-manifest.json is missing`,
|
|
41
|
+
{
|
|
42
|
+
artifactRefs: [manifestPath],
|
|
43
|
+
rescueAction: 'run cc-plan to create planning/task-manifest.json before approving execution'
|
|
44
|
+
}
|
|
45
|
+
);
|
|
31
46
|
}
|
|
32
47
|
|
|
33
48
|
const state = parseRuntimeState(rawState);
|
|
@@ -17,6 +17,7 @@ const {
|
|
|
17
17
|
} = require('../store');
|
|
18
18
|
const { parseReportCard, parseManifest } = require('../schemas');
|
|
19
19
|
const { syncIntentMemory } = require('../intent');
|
|
20
|
+
const { assertShipReady } = require('../readiness');
|
|
20
21
|
|
|
21
22
|
function formatReleaseNote({ changeId, manifest, report }) {
|
|
22
23
|
const passedTasks = manifest.tasks.filter((task) => task.status === 'passed');
|
|
@@ -59,9 +60,11 @@ async function runRelease({ repoRoot, changeId }) {
|
|
|
59
60
|
const manifest = parseManifest(await readJson(manifestPath));
|
|
60
61
|
const previousState = await readJson(statePath, null);
|
|
61
62
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
assertShipReady(report, {
|
|
64
|
+
reportPath,
|
|
65
|
+
errorName: 'ReleaseReadinessError',
|
|
66
|
+
rescueAction: 'run cc-check until ship-readiness is ready before release'
|
|
67
|
+
});
|
|
65
68
|
|
|
66
69
|
const note = formatReleaseNote({ changeId, manifest, report });
|
|
67
70
|
const releaseNotePath = getReleaseNotePath(repoRoot, changeId);
|
|
@@ -33,6 +33,7 @@ const {
|
|
|
33
33
|
const { parseCheckpoint, parseManifest, parseRuntimeState } = require('../schemas');
|
|
34
34
|
const { applyManifestExecutionState } = require('../planner');
|
|
35
35
|
const { syncIntentMemory } = require('../intent');
|
|
36
|
+
const { namedError } = require('../errors');
|
|
36
37
|
|
|
37
38
|
function quoteShellArg(value) {
|
|
38
39
|
return `'${String(value).replace(/'/g, `'\"'\"'`)}'`;
|
|
@@ -136,6 +137,34 @@ async function buildTaskBrief(repoRoot, changeId, taskId, planVersion) {
|
|
|
136
137
|
].join('\n');
|
|
137
138
|
}
|
|
138
139
|
|
|
140
|
+
async function assertWorkerPlanFresh(repoRoot, changeId, handoff) {
|
|
141
|
+
const manifestPath = getTaskManifestPath(repoRoot, changeId);
|
|
142
|
+
const manifest = parseManifest(await readJson(manifestPath));
|
|
143
|
+
const currentPlanVersion = manifest.metadata?.planVersion || 1;
|
|
144
|
+
|
|
145
|
+
if (currentPlanVersion === handoff.planVersion) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
throw namedError(
|
|
150
|
+
'StalePlanVersionError',
|
|
151
|
+
`Worker ${handoff.workerId} was assigned to planVersion ${handoff.planVersion}, but current planVersion is ${currentPlanVersion}`,
|
|
152
|
+
{
|
|
153
|
+
artifactRefs: [
|
|
154
|
+
`devflow/changes/<change>/planning/task-manifest.json`,
|
|
155
|
+
handoff.assignmentPath
|
|
156
|
+
],
|
|
157
|
+
rescueAction: 'rerun delegation sync for current planVersion before worker-run',
|
|
158
|
+
details: {
|
|
159
|
+
workerId: handoff.workerId,
|
|
160
|
+
assignedPlanVersion: handoff.planVersion,
|
|
161
|
+
currentPlanVersion,
|
|
162
|
+
manifestPath
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
139
168
|
async function buildProviderPrompt(repoRootOrHandoff, handoffOrTaskId, maybeTaskId) {
|
|
140
169
|
const repoRoot = typeof repoRootOrHandoff === 'string' ? repoRootOrHandoff : process.cwd();
|
|
141
170
|
const handoff = typeof repoRootOrHandoff === 'string' ? handoffOrTaskId : repoRootOrHandoff;
|
|
@@ -317,6 +346,7 @@ async function runWorkerCommand({
|
|
|
317
346
|
}
|
|
318
347
|
|
|
319
348
|
const handoff = await buildWorkerHandoff(repoRoot, changeId, workerId);
|
|
349
|
+
await assertWorkerPlanFresh(repoRoot, changeId, handoff);
|
|
320
350
|
const primaryTaskId = pickTaskId(handoff, taskId);
|
|
321
351
|
const assignment = {
|
|
322
352
|
workerId,
|
|
@@ -66,6 +66,14 @@ function parseCsvLike(rawText) {
|
|
|
66
66
|
.filter(Boolean);
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
function quoteShellArg(value) {
|
|
70
|
+
return `'${String(value).replace(/'/g, `'\"'\"'`)}'`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function buildTaskRunCommand(taskId, title) {
|
|
74
|
+
return `printf '%s\\n' ${quoteShellArg(`[TASK ${taskId}] ${title}`)}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
69
77
|
function parseFieldValues(rawValue) {
|
|
70
78
|
const inlineRefs = parseInlineCodeRefs(rawValue);
|
|
71
79
|
if (inlineRefs.length > 0) {
|
|
@@ -245,7 +253,7 @@ function parseTasksMarkdown(content) {
|
|
|
245
253
|
dependsOn,
|
|
246
254
|
touches,
|
|
247
255
|
files: inlineFiles,
|
|
248
|
-
run: [
|
|
256
|
+
run: [buildTaskRunCommand(taskId, title)],
|
|
249
257
|
checks: [],
|
|
250
258
|
acceptance: [],
|
|
251
259
|
verification: [],
|
|
@@ -420,7 +428,7 @@ function buildDefaultTasks(changeId) {
|
|
|
420
428
|
dependsOn: [],
|
|
421
429
|
touches: [],
|
|
422
430
|
files: [],
|
|
423
|
-
run: [
|
|
431
|
+
run: [buildTaskRunCommand('T001', `Bootstrap ${changeId}`)],
|
|
424
432
|
checks: [],
|
|
425
433
|
acceptance: ['Bootstrap the requirement workspace'],
|
|
426
434
|
verification: ['echo "[TASK T001] Bootstrap complete"'],
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [INPUT]: 接收 query id、repoRoot/changeId 与只读 handler registry。
|
|
3
|
+
* [OUTPUT]: 返回 typed query result:ok/data 或 named error,并附 operational trace。
|
|
4
|
+
* [POS]: skill runtime 的查询分发表,只读派生已有 artifact,不承载 workflow 语义。
|
|
5
|
+
* [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
|
|
10
|
+
const { namedError, serializeError } = require('./errors');
|
|
11
|
+
const { createTrace } = require('./trace');
|
|
12
|
+
|
|
13
|
+
function resolveArtifactRefs(entry, context, key) {
|
|
14
|
+
const refs = entry[key];
|
|
15
|
+
return typeof refs === 'function' ? refs(context) : refs || [];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function createQueryRegistry(entries) {
|
|
19
|
+
const registry = new Map(entries.map((entry) => [entry.id, entry]));
|
|
20
|
+
|
|
21
|
+
function listQueryIds() {
|
|
22
|
+
return [...registry.keys()].sort();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function runQuery(queryId, context = {}) {
|
|
26
|
+
const entry = registry.get(queryId);
|
|
27
|
+
|
|
28
|
+
if (!entry) {
|
|
29
|
+
const supported = listQueryIds();
|
|
30
|
+
const error = namedError(
|
|
31
|
+
'UnknownQueryError',
|
|
32
|
+
`Unknown query id: ${queryId}`,
|
|
33
|
+
{
|
|
34
|
+
rescueAction: `use one of: ${supported.join(', ')}`,
|
|
35
|
+
details: { supported }
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
ok: false,
|
|
41
|
+
queryId,
|
|
42
|
+
error: serializeError(error),
|
|
43
|
+
trace: createTrace({
|
|
44
|
+
event: 'query.unknown',
|
|
45
|
+
changeId: context.changeId,
|
|
46
|
+
nextAction: 'choose-supported-query'
|
|
47
|
+
})
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const artifactRefs = resolveArtifactRefs(entry, context, 'artifactRefs');
|
|
52
|
+
const requiredArtifactRefs = resolveArtifactRefs(entry, context, 'requiredArtifactRefs');
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const missingRefs = requiredArtifactRefs.filter((ref) => !fs.existsSync(ref));
|
|
56
|
+
if (missingRefs.length > 0) {
|
|
57
|
+
throw namedError(
|
|
58
|
+
'MissingQueryArtifactError',
|
|
59
|
+
`Missing required query artifact: ${missingRefs.join(', ')}`,
|
|
60
|
+
{
|
|
61
|
+
artifactRefs: missingRefs,
|
|
62
|
+
rescueAction: 'create required runtime artifacts before running this query'
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
ok: true,
|
|
69
|
+
queryId,
|
|
70
|
+
data: await entry.handler(context),
|
|
71
|
+
trace: createTrace({
|
|
72
|
+
event: `query.${queryId}`,
|
|
73
|
+
changeId: context.changeId,
|
|
74
|
+
artifactRefs,
|
|
75
|
+
nextAction: entry.nextAction || 'read-query-result'
|
|
76
|
+
})
|
|
77
|
+
};
|
|
78
|
+
} catch (error) {
|
|
79
|
+
return {
|
|
80
|
+
ok: false,
|
|
81
|
+
queryId,
|
|
82
|
+
error: serializeError(error),
|
|
83
|
+
trace: createTrace({
|
|
84
|
+
event: `query.${queryId}.failed`,
|
|
85
|
+
changeId: context.changeId,
|
|
86
|
+
artifactRefs,
|
|
87
|
+
nextAction: error.rescueAction || 'inspect-runtime-artifacts'
|
|
88
|
+
})
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
listQueryIds,
|
|
95
|
+
runQuery
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
module.exports = {
|
|
100
|
+
createQueryRegistry
|
|
101
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* [INPUT]: 依赖 store/artifacts/lifecycle 读取 requirement 工件,接收 repoRoot 和 changeId。
|
|
3
|
-
* [OUTPUT]: 对外提供
|
|
3
|
+
* [OUTPUT]: 对外提供 typed query registry 与兼容查询函数,附 named error 和 trace shape。
|
|
4
4
|
* [POS]: skill runtime 的薄查询兼容层,只读 artifact 与共享 lifecycle 语义,不再自带流程推导副本。
|
|
5
5
|
* [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
|
|
6
6
|
*/
|
|
@@ -20,6 +20,44 @@ const {
|
|
|
20
20
|
deriveTaskProgress,
|
|
21
21
|
isTaskCompletedStatus
|
|
22
22
|
} = require('./lifecycle');
|
|
23
|
+
const { createQueryRegistry } = require('./query-registry');
|
|
24
|
+
const { namedError } = require('./errors');
|
|
25
|
+
const { deriveShipReadiness } = require('./readiness');
|
|
26
|
+
|
|
27
|
+
async function readQueryArtifact(filePath, { required = true } = {}) {
|
|
28
|
+
try {
|
|
29
|
+
const value = await readJson(filePath, null);
|
|
30
|
+
|
|
31
|
+
if (required && value === null) {
|
|
32
|
+
throw namedError(
|
|
33
|
+
'MissingQueryArtifactError',
|
|
34
|
+
`Missing required query artifact: ${filePath}`,
|
|
35
|
+
{
|
|
36
|
+
artifactRefs: [filePath],
|
|
37
|
+
rescueAction: 'create required runtime artifacts before running this query'
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return value;
|
|
43
|
+
} catch (error) {
|
|
44
|
+
if (error.name === 'MissingQueryArtifactError') {
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
throw namedError(
|
|
49
|
+
'InvalidQueryArtifactError',
|
|
50
|
+
`Invalid query artifact ${filePath}: ${error.message}`,
|
|
51
|
+
{
|
|
52
|
+
artifactRefs: [filePath],
|
|
53
|
+
rescueAction: 'repair or regenerate the invalid runtime artifact before running this query',
|
|
54
|
+
details: {
|
|
55
|
+
cause: error.name || 'Error'
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
23
61
|
|
|
24
62
|
/**
|
|
25
63
|
* 获取任务进度统计
|
|
@@ -29,21 +67,8 @@ const {
|
|
|
29
67
|
*/
|
|
30
68
|
async function getProgress(repoRoot, changeId) {
|
|
31
69
|
const manifestPath = getTaskManifestPath(repoRoot, changeId);
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const manifest = await readJson(manifestPath);
|
|
35
|
-
return deriveTaskProgress(manifest.tasks || []);
|
|
36
|
-
} catch (error) {
|
|
37
|
-
return {
|
|
38
|
-
totalTasks: 0,
|
|
39
|
-
completedTasks: 0,
|
|
40
|
-
failedTasks: 0,
|
|
41
|
-
pendingTasks: 0,
|
|
42
|
-
runningTasks: 0,
|
|
43
|
-
skippedTasks: 0,
|
|
44
|
-
error: error.message
|
|
45
|
-
};
|
|
46
|
-
}
|
|
70
|
+
const manifest = await readQueryArtifact(manifestPath);
|
|
71
|
+
return deriveTaskProgress(manifest.tasks || []);
|
|
47
72
|
}
|
|
48
73
|
|
|
49
74
|
/**
|
|
@@ -54,41 +79,36 @@ async function getProgress(repoRoot, changeId) {
|
|
|
54
79
|
*/
|
|
55
80
|
async function getNextTask(repoRoot, changeId) {
|
|
56
81
|
const manifestPath = getTaskManifestPath(repoRoot, changeId);
|
|
82
|
+
const manifest = await readQueryArtifact(manifestPath);
|
|
83
|
+
const executionState = deriveManifestExecutionState(manifest.tasks || []);
|
|
84
|
+
const activePhase = manifest.activePhase ?? executionState.activePhase;
|
|
85
|
+
const completedIds = new Set(
|
|
86
|
+
(manifest.tasks || [])
|
|
87
|
+
.filter((task) => isTaskCompletedStatus(task.status))
|
|
88
|
+
.map((task) => task.id)
|
|
89
|
+
);
|
|
90
|
+
const currentTaskId = manifest.currentTaskId ?? executionState.currentTaskId;
|
|
57
91
|
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const completedIds = new Set(
|
|
63
|
-
(manifest.tasks || [])
|
|
64
|
-
.filter((task) => isTaskCompletedStatus(task.status))
|
|
65
|
-
.map((task) => task.id)
|
|
66
|
-
);
|
|
67
|
-
const currentTaskId = manifest.currentTaskId ?? executionState.currentTaskId;
|
|
68
|
-
|
|
69
|
-
if (currentTaskId) {
|
|
70
|
-
const currentTask = manifest.tasks.find((task) => task.id === currentTaskId);
|
|
71
|
-
if (currentTask && currentTask.status === 'pending') {
|
|
72
|
-
return currentTask;
|
|
73
|
-
}
|
|
92
|
+
if (currentTaskId) {
|
|
93
|
+
const currentTask = manifest.tasks.find((task) => task.id === currentTaskId);
|
|
94
|
+
if (currentTask && currentTask.status === 'pending') {
|
|
95
|
+
return currentTask;
|
|
74
96
|
}
|
|
97
|
+
}
|
|
75
98
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
99
|
+
const nextTask = (manifest.tasks || []).find((task) => {
|
|
100
|
+
if (task.status !== 'pending') {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
80
103
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
104
|
+
if (activePhase !== null && activePhase !== undefined && (task.phase || 1) !== activePhase) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
84
107
|
|
|
85
|
-
|
|
86
|
-
|
|
108
|
+
return (task.dependsOn || []).every((depId) => completedIds.has(depId));
|
|
109
|
+
});
|
|
87
110
|
|
|
88
|
-
|
|
89
|
-
} catch (error) {
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
111
|
+
return nextTask || null;
|
|
92
112
|
}
|
|
93
113
|
|
|
94
114
|
/**
|
|
@@ -102,56 +122,104 @@ async function getFullState(repoRoot, changeId) {
|
|
|
102
122
|
const reportPath = getReportCardPath(repoRoot, changeId);
|
|
103
123
|
const prBriefPath = getIntentPrBriefPath(repoRoot, changeId);
|
|
104
124
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
125
|
+
const [state, manifest, hasPrBrief] = await Promise.all([
|
|
126
|
+
readQueryArtifact(statePath),
|
|
127
|
+
readQueryArtifact(getTaskManifestPath(repoRoot, changeId)),
|
|
128
|
+
exists(prBriefPath)
|
|
129
|
+
]);
|
|
130
|
+
const progress = await getProgress(repoRoot, changeId);
|
|
131
|
+
const nextTask = await getNextTask(repoRoot, changeId);
|
|
132
|
+
const report = await readQueryArtifact(reportPath, { required: false });
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
lifecycle: {
|
|
136
|
+
changeId: state.changeId,
|
|
137
|
+
goal: state.goal,
|
|
138
|
+
status: state.status,
|
|
139
|
+
initializedAt: state.initializedAt,
|
|
140
|
+
plannedAt: state.plannedAt,
|
|
141
|
+
verifiedAt: state.verifiedAt,
|
|
142
|
+
releasedAt: state.releasedAt,
|
|
143
|
+
updatedAt: state.updatedAt,
|
|
144
|
+
stage: deriveLifecycleStage({ state, manifest, report, hasPrBrief }),
|
|
145
|
+
approval: getApprovalState(state, manifest)
|
|
146
|
+
},
|
|
147
|
+
progress,
|
|
148
|
+
nextTask,
|
|
149
|
+
delivery: {
|
|
150
|
+
prBriefPath: hasPrBrief ? prBriefPath : null
|
|
151
|
+
},
|
|
152
|
+
quality: report ? {
|
|
153
|
+
overall: report.overall,
|
|
154
|
+
verdict: report.verdict || (report.overall === 'pass' ? 'pass' : 'fail'),
|
|
155
|
+
reviewStatus: report.review?.status || 'skipped',
|
|
156
|
+
reviewFindings: (report.review?.findings || []).length,
|
|
157
|
+
blockingFindings: report.blockingFindings,
|
|
158
|
+
timestamp: report.timestamp
|
|
159
|
+
} : null
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function getShipReadiness(repoRoot, changeId) {
|
|
164
|
+
const reportPath = getReportCardPath(repoRoot, changeId);
|
|
165
|
+
const report = await readJson(reportPath, null);
|
|
166
|
+
|
|
167
|
+
if (!report) {
|
|
168
|
+
throw namedError(
|
|
169
|
+
'MissingReportCardError',
|
|
170
|
+
`Missing report card for ${changeId}`,
|
|
171
|
+
{
|
|
172
|
+
artifactRefs: [reportPath],
|
|
173
|
+
rescueAction: 'run cc-check and create review/report-card.json before cc-act'
|
|
174
|
+
}
|
|
175
|
+
);
|
|
150
176
|
}
|
|
177
|
+
|
|
178
|
+
return deriveShipReadiness(report, { reportPath });
|
|
151
179
|
}
|
|
152
180
|
|
|
181
|
+
function queryArtifactRefs(repoRoot, changeId, names) {
|
|
182
|
+
const refs = {
|
|
183
|
+
manifest: getTaskManifestPath(repoRoot, changeId),
|
|
184
|
+
state: getRuntimeStatePath(repoRoot, changeId),
|
|
185
|
+
report: getReportCardPath(repoRoot, changeId),
|
|
186
|
+
prBrief: getIntentPrBriefPath(repoRoot, changeId)
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
return names.map((name) => refs[name]).filter(Boolean);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const registry = createQueryRegistry([
|
|
193
|
+
{
|
|
194
|
+
id: 'progress',
|
|
195
|
+
artifactRefs: ({ repoRoot, changeId }) => queryArtifactRefs(repoRoot, changeId, ['manifest']),
|
|
196
|
+
requiredArtifactRefs: ({ repoRoot, changeId }) => queryArtifactRefs(repoRoot, changeId, ['manifest']),
|
|
197
|
+
handler: ({ repoRoot, changeId }) => getProgress(repoRoot, changeId)
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
id: 'next-task',
|
|
201
|
+
artifactRefs: ({ repoRoot, changeId }) => queryArtifactRefs(repoRoot, changeId, ['manifest']),
|
|
202
|
+
requiredArtifactRefs: ({ repoRoot, changeId }) => queryArtifactRefs(repoRoot, changeId, ['manifest']),
|
|
203
|
+
handler: ({ repoRoot, changeId }) => getNextTask(repoRoot, changeId)
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
id: 'full-state',
|
|
207
|
+
artifactRefs: ({ repoRoot, changeId }) => queryArtifactRefs(repoRoot, changeId, ['state', 'manifest', 'report', 'prBrief']),
|
|
208
|
+
requiredArtifactRefs: ({ repoRoot, changeId }) => queryArtifactRefs(repoRoot, changeId, ['state', 'manifest']),
|
|
209
|
+
handler: ({ repoRoot, changeId }) => getFullState(repoRoot, changeId)
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
id: 'ship-readiness',
|
|
213
|
+
artifactRefs: ({ repoRoot, changeId }) => queryArtifactRefs(repoRoot, changeId, ['report']),
|
|
214
|
+
handler: ({ repoRoot, changeId }) => getShipReadiness(repoRoot, changeId)
|
|
215
|
+
}
|
|
216
|
+
]);
|
|
217
|
+
|
|
153
218
|
module.exports = {
|
|
154
219
|
getProgress,
|
|
155
220
|
getNextTask,
|
|
156
|
-
getFullState
|
|
221
|
+
getFullState,
|
|
222
|
+
getShipReadiness,
|
|
223
|
+
listQueryIds: registry.listQueryIds,
|
|
224
|
+
runQuery: registry.runQuery
|
|
157
225
|
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [INPUT]: 接收 report-card 对象与 report artifact path。
|
|
3
|
+
* [OUTPUT]: 派生 ship-readiness 结果,并在未 ready 时抛出 named error。
|
|
4
|
+
* [POS]: skill runtime 的交付就绪单一真相源,被 query/release 共享。
|
|
5
|
+
* [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { namedError } = require('./errors');
|
|
9
|
+
|
|
10
|
+
function deriveVerdict(report) {
|
|
11
|
+
return report.verdict || (report.overall === 'pass' ? 'pass' : 'fail');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function collectShipReadinessBlockers(report) {
|
|
15
|
+
const verdict = deriveVerdict(report);
|
|
16
|
+
const blockers = [];
|
|
17
|
+
|
|
18
|
+
if (report.overall !== 'pass') {
|
|
19
|
+
blockers.push('report-card overall is not pass');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (verdict !== 'pass') {
|
|
23
|
+
blockers.push(`verdict is ${verdict}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if ((report.reroute || 'none') !== 'none') {
|
|
27
|
+
blockers.push(`reroute is ${report.reroute}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (report.specSyncReady !== true) {
|
|
31
|
+
blockers.push('specSyncReady is not true');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
blockers.push(...(report.blockingFindings || []));
|
|
35
|
+
blockers.push(...(report.gaps || []));
|
|
36
|
+
return blockers;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function deriveShipReadiness(report, { reportPath = '' } = {}) {
|
|
40
|
+
const verdict = deriveVerdict(report);
|
|
41
|
+
const reroute = report.reroute || 'none';
|
|
42
|
+
const specSyncReady = report.specSyncReady === true;
|
|
43
|
+
const blockers = collectShipReadinessBlockers(report);
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
ready: blockers.length === 0,
|
|
47
|
+
verdict,
|
|
48
|
+
reroute,
|
|
49
|
+
specSyncReady,
|
|
50
|
+
blockers,
|
|
51
|
+
reportPath,
|
|
52
|
+
timestamp: report.timestamp || ''
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function assertShipReady(report, {
|
|
57
|
+
reportPath = '',
|
|
58
|
+
errorName = 'ShipReadinessError',
|
|
59
|
+
rescueAction = 'run cc-check until ship-readiness is ready'
|
|
60
|
+
} = {}) {
|
|
61
|
+
const readiness = deriveShipReadiness(report, { reportPath });
|
|
62
|
+
|
|
63
|
+
if (readiness.ready) {
|
|
64
|
+
return readiness;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
throw namedError(
|
|
68
|
+
errorName,
|
|
69
|
+
`Ship readiness blocked: ${readiness.blockers.join('; ')}`,
|
|
70
|
+
{
|
|
71
|
+
artifactRefs: reportPath ? [reportPath] : [],
|
|
72
|
+
rescueAction,
|
|
73
|
+
details: {
|
|
74
|
+
blockers: readiness.blockers
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = {
|
|
81
|
+
collectShipReadinessBlockers,
|
|
82
|
+
deriveShipReadiness,
|
|
83
|
+
assertShipReady
|
|
84
|
+
};
|