cc-devflow 4.5.8 → 4.5.10
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 +33 -0
- package/.claude/skills/cc-act/PLAYBOOK.md +9 -4
- package/.claude/skills/cc-act/SKILL.md +73 -12
- package/.claude/skills/cc-act/assets/PROJECT_POSTMORTEM_INDEX_TEMPLATE.md +30 -0
- package/.claude/skills/cc-act/assets/PROJECT_POSTMORTEM_PRINCIPLES_TEMPLATE.md +29 -0
- package/.claude/skills/cc-act/assets/PROJECT_POSTMORTEM_TEMPLATE.md +103 -0
- package/.claude/skills/cc-act/assets/PR_BRIEF_TEMPLATE.md +61 -5
- package/.claude/skills/cc-act/references/closure-contract.md +4 -1
- package/.claude/skills/cc-act/references/git-commit-guidelines.md +342 -37
- package/.claude/skills/cc-act/scripts/cc-act-common.sh +29 -1
- package/.claude/skills/cc-act/scripts/render-pr-brief.sh +164 -0
- package/.claude/skills/cc-act/scripts/sync-act-docs.sh +1 -1
- package/.claude/skills/cc-check/CHANGELOG.md +17 -0
- package/.claude/skills/cc-check/PLAYBOOK.md +1 -0
- package/.claude/skills/cc-check/SKILL.md +9 -5
- package/.claude/skills/cc-check/references/review-contract.md +7 -0
- package/.claude/skills/cc-check/scripts/render-report-card.js +6 -1
- package/.claude/skills/cc-dev/CHANGELOG.md +5 -0
- package/.claude/skills/cc-dev/SKILL.md +26 -1
- package/.claude/skills/cc-do/CHANGELOG.md +23 -0
- package/.claude/skills/cc-do/PLAYBOOK.md +7 -7
- package/.claude/skills/cc-do/SKILL.md +49 -45
- package/.claude/skills/cc-do/references/execution-recovery.md +18 -13
- package/.claude/skills/cc-do/scripts/build-task-context.sh +13 -22
- package/.claude/skills/cc-do/scripts/mark-task-complete.sh +0 -6
- package/.claude/skills/cc-do/scripts/record-review-decision.sh +4 -5
- package/.claude/skills/cc-do/scripts/recover-workflow.sh +9 -11
- package/.claude/skills/cc-do/scripts/verify-task-gates.sh +12 -10
- package/.claude/skills/cc-do/scripts/write-task-checkpoint.sh +7 -29
- package/.claude/skills/cc-investigate/CHANGELOG.md +34 -0
- package/.claude/skills/cc-investigate/PLAYBOOK.md +21 -5
- package/.claude/skills/cc-investigate/SKILL.md +97 -40
- package/.claude/skills/cc-investigate/assets/TASKS_TEMPLATE.md +66 -4
- package/.claude/skills/cc-investigate/assets/TASK_MANIFEST_TEMPLATE.json +30 -59
- package/.claude/skills/cc-investigate/assets/{ANALYSIS_TEMPLATE.md → legacy/ANALYSIS_TEMPLATE.md} +48 -0
- package/.claude/skills/cc-investigate/references/investigation-contract.md +16 -2
- package/.claude/skills/cc-investigate/scripts/bootstrap-analysis.sh +1 -1
- package/.claude/skills/cc-next/CHANGELOG.md +6 -0
- package/.claude/skills/cc-next/PLAYBOOK.md +26 -4
- package/.claude/skills/cc-next/SKILL.md +39 -4
- package/.claude/skills/cc-plan/CHANGELOG.md +38 -0
- package/.claude/skills/cc-plan/PLAYBOOK.md +60 -53
- package/.claude/skills/cc-plan/SKILL.md +164 -87
- package/.claude/skills/cc-plan/assets/TASKS_TEMPLATE.md +101 -9
- package/.claude/skills/cc-plan/assets/TASK_MANIFEST_TEMPLATE.json +58 -229
- package/.claude/skills/cc-plan/assets/{DESIGN_TEMPLATE.md → legacy/DESIGN_TEMPLATE.md} +68 -0
- package/.claude/skills/cc-plan/assets/{TINY_DESIGN_TEMPLATE.md → legacy/TINY_DESIGN_TEMPLATE.md} +47 -1
- package/.claude/skills/cc-plan/references/planning-contract.md +48 -33
- package/.claude/skills/cc-review/CHANGELOG.md +6 -0
- package/.claude/skills/cc-review/PLAYBOOK.md +9 -11
- package/.claude/skills/cc-review/SKILL.md +37 -61
- package/.claude/skills/cc-review/references/e2e-and-plugin-verification.md +1 -1
- package/.claude/skills/cc-review/references/implementation-review-branch.md +5 -5
- package/.claude/skills/cc-review/references/plan-review-branch.md +1 -1
- package/.claude/skills/cc-review/references/review-methods.md +4 -4
- package/.claude/skills/cc-review/scripts/collect-review-context.sh +14 -7
- package/.claude/skills/cc-roadmap/CHANGELOG.md +6 -0
- package/.claude/skills/cc-roadmap/PLAYBOOK.md +30 -0
- package/.claude/skills/cc-roadmap/SKILL.md +45 -8
- package/.claude/skills/cc-roadmap/assets/BACKLOG_TEMPLATE.md +8 -0
- package/.claude/skills/cc-roadmap/assets/ROADMAP_TEMPLATE.md +22 -0
- package/.claude/skills/cc-roadmap/assets/TRACKING_TEMPLATE.json +32 -1
- package/.claude/skills/cc-roadmap/references/roadmap-dialogue.md +14 -14
- package/CHANGELOG.md +28 -0
- package/CONTRIBUTING.md +40 -4
- package/CONTRIBUTING.zh-CN.md +40 -4
- package/README.md +57 -43
- package/README.zh-CN.md +57 -43
- package/bin/cc-devflow-cli.js +293 -36
- package/docs/examples/START-HERE.md +5 -4
- package/docs/examples/example-bindings.json +10 -10
- package/docs/examples/full-design-blocked/BACKLOG.md +1 -1
- package/docs/examples/full-design-blocked/README.md +2 -2
- 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 +2 -1
- package/docs/examples/full-design-blocked/changes/REQ-002-bulk-invite-import/planning/task-manifest.json +29 -312
- package/docs/examples/full-design-blocked/changes/REQ-002-bulk-invite-import/planning/tasks.md +11 -8
- package/docs/examples/full-design-blocked/changes/REQ-002-bulk-invite-import/review/report-card.json +4 -4
- package/docs/examples/full-design-blocked/roadmap.json +1 -1
- package/docs/examples/local-handoff/BACKLOG.md +1 -1
- package/docs/examples/local-handoff/README.md +2 -2
- package/docs/examples/local-handoff/ROADMAP.md +1 -1
- package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/planning/design.md +2 -1
- package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/planning/task-manifest.json +27 -210
- package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/planning/tasks.md +9 -6
- package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/review/report-card.json +1 -1
- package/docs/examples/local-handoff/roadmap.json +1 -1
- package/docs/examples/pdca-loop/BACKLOG.md +1 -1
- package/docs/examples/pdca-loop/README.md +2 -2
- package/docs/examples/pdca-loop/ROADMAP.md +1 -1
- package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/handoff/pr-brief.md +65 -1
- package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/planning/design.md +2 -1
- package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/planning/task-manifest.json +26 -228
- package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/planning/tasks.md +9 -6
- package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/review/report-card.json +1 -1
- package/docs/examples/pdca-loop/roadmap.json +1 -1
- package/docs/examples/scripts/check-example-bindings.sh +11 -5
- package/docs/get-shit-done-strategy-audit.md +22 -22
- package/docs/guides/artifact-contract.md +44 -0
- package/docs/guides/getting-started.md +10 -8
- package/docs/guides/getting-started.zh-CN.md +10 -8
- package/docs/guides/minimize-artifacts.md +123 -0
- package/docs/guides/project-postmortem.md +78 -0
- package/lib/compiler/__tests__/skills-registry.test.js +2 -2
- package/lib/skill-runtime/CLAUDE.md +1 -1
- package/lib/skill-runtime/__tests__/autopilot.test.js +42 -6
- package/lib/skill-runtime/__tests__/benchmark-artifacts.test.js +165 -0
- package/lib/skill-runtime/__tests__/cli-bootstrap.integration.test.js +2 -2
- package/lib/skill-runtime/__tests__/dispatch.test.js +8 -38
- package/lib/skill-runtime/__tests__/intent.test.js +4 -20
- package/lib/skill-runtime/__tests__/lifecycle.test.js +1 -1
- package/lib/skill-runtime/__tests__/paths.test.js +7 -1
- package/lib/skill-runtime/__tests__/planner.tdd.test.js +63 -2
- package/lib/skill-runtime/__tests__/prepare-pr.test.js +3 -16
- package/lib/skill-runtime/__tests__/query.test.js +388 -7
- package/lib/skill-runtime/__tests__/review-check-integration.test.js +148 -0
- package/lib/skill-runtime/__tests__/review-records.test.js +619 -0
- package/lib/skill-runtime/__tests__/runtime.integration.test.js +64 -23
- package/lib/skill-runtime/__tests__/schemas.test.js +76 -2
- package/lib/skill-runtime/__tests__/task-contract-migrate.test.js +137 -0
- package/lib/skill-runtime/__tests__/task-contract.test.js +783 -0
- package/lib/skill-runtime/__tests__/verify-artifacts.test.js +203 -0
- package/lib/skill-runtime/__tests__/worker-run.test.js +4 -11
- package/lib/skill-runtime/__tests__/workflow-context-legacy-fallback.test.js +31 -0
- package/lib/skill-runtime/__tests__/workflow-context.test.js +98 -0
- package/lib/skill-runtime/artifacts.js +0 -5
- package/lib/skill-runtime/context-index.js +545 -0
- package/lib/skill-runtime/intent.js +9 -33
- package/lib/skill-runtime/lifecycle.js +1 -1
- package/lib/skill-runtime/operations/CLAUDE.md +2 -2
- package/lib/skill-runtime/operations/dispatch.js +4 -42
- package/lib/skill-runtime/operations/init.js +2 -6
- package/lib/skill-runtime/operations/janitor.js +2 -18
- package/lib/skill-runtime/operations/resume.js +21 -38
- package/lib/skill-runtime/operations/review-records.js +265 -0
- package/lib/skill-runtime/operations/snapshot.js +1 -1
- package/lib/skill-runtime/operations/task-contract.js +524 -0
- package/lib/skill-runtime/operations/worker-run.js +2 -30
- package/lib/skill-runtime/paths.js +4 -4
- package/lib/skill-runtime/planner.js +25 -13
- package/lib/skill-runtime/query-registry.js +2 -2
- package/lib/skill-runtime/query.js +16 -3
- package/lib/skill-runtime/review-records.js +123 -0
- package/lib/skill-runtime/review.js +246 -11
- package/lib/skill-runtime/schemas.js +179 -15
- package/lib/skill-runtime/store.js +0 -10
- package/lib/skill-runtime/task-contract.js +187 -0
- package/lib/skill-runtime/workflow-context.js +748 -0
- package/package.json +7 -4
|
@@ -0,0 +1,748 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [INPUT]: 依赖 task-manifest/report/change-meta/runtime-state 等已有 artifact,接收 repoRoot/changeId/changeKey。
|
|
3
|
+
* [OUTPUT]: 生成 compact workflow context,告诉 PDCA/IDCA 下一步、默认读取集、可信命令与深层展开条件。
|
|
4
|
+
* [POS]: skill runtime 的渐进式披露查询层,只读派生状态,不拥有 workflow 规则本身。
|
|
5
|
+
* [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const {
|
|
11
|
+
getRuntimeStatePath,
|
|
12
|
+
getTaskManifestPath,
|
|
13
|
+
getReportCardPath,
|
|
14
|
+
getTasksMarkdownPath,
|
|
15
|
+
exists,
|
|
16
|
+
readJson,
|
|
17
|
+
readText
|
|
18
|
+
} = require('./store');
|
|
19
|
+
const { getIntentPrBriefPath } = require('./artifacts');
|
|
20
|
+
const { getChangePaths } = require('./paths');
|
|
21
|
+
const { deriveManifestExecutionState } = require('./planner');
|
|
22
|
+
const {
|
|
23
|
+
getApprovalState,
|
|
24
|
+
deriveLifecycleStage,
|
|
25
|
+
deriveTaskProgress,
|
|
26
|
+
isTaskCompletedStatus
|
|
27
|
+
} = require('./lifecycle');
|
|
28
|
+
const { namedError } = require('./errors');
|
|
29
|
+
const { deriveShipReadiness } = require('./readiness');
|
|
30
|
+
const {
|
|
31
|
+
buildDefaultOpenRefs,
|
|
32
|
+
buildMustNotForget,
|
|
33
|
+
buildPacketOnly,
|
|
34
|
+
buildSourceHashes,
|
|
35
|
+
buildSourceIndex,
|
|
36
|
+
dedupeOpenRefs,
|
|
37
|
+
deepOpenGroup,
|
|
38
|
+
openRef,
|
|
39
|
+
validateDeepOpenGroups,
|
|
40
|
+
validateOpenRefs,
|
|
41
|
+
withFragment
|
|
42
|
+
} = require('./context-index');
|
|
43
|
+
|
|
44
|
+
const RESUMABLE_TASK_STATUSES = new Set([
|
|
45
|
+
'pending',
|
|
46
|
+
'running',
|
|
47
|
+
'in_progress',
|
|
48
|
+
'active',
|
|
49
|
+
'blocked',
|
|
50
|
+
'needs_fix',
|
|
51
|
+
'failed'
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
async function readWorkflowArtifact(filePath, { required = true } = {}) {
|
|
55
|
+
try {
|
|
56
|
+
const value = await readJson(filePath, null);
|
|
57
|
+
|
|
58
|
+
if (required && value === null) {
|
|
59
|
+
throw namedError(
|
|
60
|
+
'MissingQueryArtifactError',
|
|
61
|
+
`Missing required query artifact: ${filePath}`,
|
|
62
|
+
{
|
|
63
|
+
artifactRefs: [filePath],
|
|
64
|
+
rescueAction: 'create required workflow artifacts before running this query'
|
|
65
|
+
}
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return value;
|
|
70
|
+
} catch (error) {
|
|
71
|
+
if (error.name === 'MissingQueryArtifactError') {
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
throw namedError(
|
|
76
|
+
'InvalidQueryArtifactError',
|
|
77
|
+
`Invalid query artifact ${filePath}: ${error.message}`,
|
|
78
|
+
{
|
|
79
|
+
artifactRefs: [filePath],
|
|
80
|
+
rescueAction: 'repair or regenerate the invalid workflow artifact before running this query',
|
|
81
|
+
details: {
|
|
82
|
+
cause: error.name || 'Error'
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function relativePath(repoRoot, filePath) {
|
|
90
|
+
return path.relative(repoRoot, filePath) || filePath;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function maybeRelativePath(repoRoot, filePath) {
|
|
94
|
+
return (await exists(filePath)) ? relativePath(repoRoot, filePath) : null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function dedupe(values) {
|
|
98
|
+
return [...new Set((values || []).filter(Boolean))];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function normalizeTaskReadFile(filePath, refs) {
|
|
102
|
+
const value = String(filePath || '').trim().replace(/^\.\//, '');
|
|
103
|
+
if (!value) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (path.isAbsolute(value)) {
|
|
108
|
+
return relativePath(refs.repoRoot, value);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const artifactAliases = new Map([
|
|
112
|
+
['design.md', refs.relativeDesignPath],
|
|
113
|
+
['analysis.md', refs.relativeAnalysisPath],
|
|
114
|
+
['tasks.md', refs.relativeTasksPath],
|
|
115
|
+
['task-manifest.json', refs.relativeManifestPath],
|
|
116
|
+
['change-meta.json', refs.relativeChangeMetaPath],
|
|
117
|
+
['report-card.json', refs.relativeReportPath],
|
|
118
|
+
['change-state.json', refs.relativeStatePath]
|
|
119
|
+
]);
|
|
120
|
+
const normalizedDir = path.dirname(value).replace(/\\/g, '/');
|
|
121
|
+
const basename = path.basename(value);
|
|
122
|
+
|
|
123
|
+
if (normalizedDir === '.' && artifactAliases.has(value)) {
|
|
124
|
+
return artifactAliases.get(value) || null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (normalizedDir === 'planning' && artifactAliases.has(basename)) {
|
|
128
|
+
return artifactAliases.get(basename) || null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (normalizedDir === 'review' && basename === 'report-card.json') {
|
|
132
|
+
return refs.relativeReportPath || null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (normalizedDir === 'meta' && basename === 'change-state.json') {
|
|
136
|
+
return refs.relativeStatePath || null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return value;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function normalizeTaskReadFiles(readFiles, refs) {
|
|
143
|
+
return dedupe((readFiles || []).map((filePath) => normalizeTaskReadFile(filePath, refs)));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function isArtifactReadFile(filePath, refs) {
|
|
147
|
+
return [
|
|
148
|
+
refs.relativeDesignPath,
|
|
149
|
+
refs.relativeAnalysisPath,
|
|
150
|
+
refs.relativeTasksPath,
|
|
151
|
+
refs.relativeManifestPath,
|
|
152
|
+
refs.relativeChangeMetaPath,
|
|
153
|
+
refs.relativeStatePath,
|
|
154
|
+
refs.relativeReportPath
|
|
155
|
+
].filter(Boolean).includes(filePath);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function summarizeTask(task) {
|
|
159
|
+
if (!task) {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
id: task.id,
|
|
165
|
+
title: task.title || task.id,
|
|
166
|
+
type: task.type || 'OTHER',
|
|
167
|
+
phase: task.phase || 1,
|
|
168
|
+
status: task.status || 'pending',
|
|
169
|
+
tddPhase: task.tddPhase || null,
|
|
170
|
+
dependsOn: task.dependsOn || [],
|
|
171
|
+
parallel: Boolean(task.parallel),
|
|
172
|
+
touches: task.touches || task.files || [],
|
|
173
|
+
files: task.files || [],
|
|
174
|
+
verification: task.verification || [],
|
|
175
|
+
evidence: task.evidence || [],
|
|
176
|
+
context: {
|
|
177
|
+
readFiles: task.context?.readFiles || [],
|
|
178
|
+
commands: task.context?.commands || [],
|
|
179
|
+
notes: task.context?.notes || []
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function deriveTaskQueues(manifest) {
|
|
185
|
+
const tasks = manifest.tasks || [];
|
|
186
|
+
const executionState = deriveManifestExecutionState(tasks);
|
|
187
|
+
const activePhase = executionState.activePhase;
|
|
188
|
+
const completedIds = new Set(
|
|
189
|
+
tasks.filter((task) => isTaskCompletedStatus(task.status)).map((task) => task.id)
|
|
190
|
+
);
|
|
191
|
+
const unfinished = tasks.filter((task) => !isTaskCompletedStatus(task.status));
|
|
192
|
+
|
|
193
|
+
function waitingOn(task) {
|
|
194
|
+
return (task.dependsOn || []).filter((depId) => !completedIds.has(depId));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const activeTasks = unfinished.filter((task) => (
|
|
198
|
+
activePhase === null || activePhase === undefined || (task.phase || 1) === activePhase
|
|
199
|
+
));
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
activePhase,
|
|
203
|
+
readyTasks: activeTasks
|
|
204
|
+
.filter((task) => (task.status || 'pending') === 'pending' && waitingOn(task).length === 0)
|
|
205
|
+
.map((task) => ({
|
|
206
|
+
...summarizeTask(task),
|
|
207
|
+
waitingOn: []
|
|
208
|
+
})),
|
|
209
|
+
runningTasks: activeTasks
|
|
210
|
+
.filter((task) => (task.status || 'pending') === 'running')
|
|
211
|
+
.map(summarizeTask),
|
|
212
|
+
blockedTasks: activeTasks
|
|
213
|
+
.filter((task) => !['pending', 'running'].includes(task.status || 'pending') || waitingOn(task).length > 0)
|
|
214
|
+
.map((task) => ({
|
|
215
|
+
...summarizeTask(task),
|
|
216
|
+
waitingOn: waitingOn(task)
|
|
217
|
+
})),
|
|
218
|
+
deferredTasks: unfinished
|
|
219
|
+
.filter((task) => activePhase !== null && activePhase !== undefined && (task.phase || 1) !== activePhase)
|
|
220
|
+
.map(summarizeTask)
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function selectNextTask(manifest, queues) {
|
|
225
|
+
const currentTaskId = manifest.currentTaskId;
|
|
226
|
+
|
|
227
|
+
if (currentTaskId) {
|
|
228
|
+
const currentTask = (manifest.tasks || []).find((task) => task.id === currentTaskId);
|
|
229
|
+
if (currentTask && RESUMABLE_TASK_STATUSES.has(currentTask.status || 'pending')) {
|
|
230
|
+
return currentTask;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const runningTask = queues.runningTasks[0]
|
|
235
|
+
|| queues.blockedTasks.find((task) => RESUMABLE_TASK_STATUSES.has(task.status) && task.status !== 'pending');
|
|
236
|
+
if (runningTask) {
|
|
237
|
+
return (manifest.tasks || []).find((task) => task.id === runningTask.id) || runningTask;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const nextReady = queues.readyTasks[0];
|
|
241
|
+
if (!nextReady) {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return (manifest.tasks || []).find((task) => task.id === nextReady.id) || null;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function verificationCommandsForTask(task) {
|
|
249
|
+
return dedupe([
|
|
250
|
+
...(task?.context?.commands || []),
|
|
251
|
+
...(task?.verification || []),
|
|
252
|
+
...(task?.run || [])
|
|
253
|
+
]);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function deriveCommandsToTrust({ manifest, nextTask, progress }) {
|
|
257
|
+
if (nextTask) {
|
|
258
|
+
return verificationCommandsForTask(nextTask);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (progress.totalTasks > 0 && progress.completedTasks === progress.totalTasks) {
|
|
262
|
+
return dedupe(
|
|
263
|
+
(manifest.tasks || [])
|
|
264
|
+
.filter((task) => isTaskCompletedStatus(task.status))
|
|
265
|
+
.flatMap(verificationCommandsForTask)
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return [];
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function markdownHasHeading(markdown, heading) {
|
|
273
|
+
const escaped = heading.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
274
|
+
return new RegExp(`(^|\\n)${escaped}\\s*(\\n|$)`, 'i').test(String(markdown || ''));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function resolveLegacyContractFragment(markdown) {
|
|
278
|
+
const candidates = [
|
|
279
|
+
['## Progressive Disclosure Index', 'progressive-disclosure-index'],
|
|
280
|
+
['## Frozen Design Card', 'frozen-design-card'],
|
|
281
|
+
['## Approved Direction', 'approved-direction'],
|
|
282
|
+
['## Requirement Snapshot', 'requirement-snapshot'],
|
|
283
|
+
['## Success Criteria', 'success-criteria'],
|
|
284
|
+
['## Validation Strategy', 'validation-strategy'],
|
|
285
|
+
['## Review Gate', 'review-gate'],
|
|
286
|
+
['# DESIGN', 'design'],
|
|
287
|
+
['# ANALYSIS', 'analysis']
|
|
288
|
+
];
|
|
289
|
+
|
|
290
|
+
const match = candidates.find(([heading]) => markdownHasHeading(markdown, heading));
|
|
291
|
+
return match ? match[1] : '';
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async function resolveCanonicalContractPath({ changeId, tasksPath, designPath, analysisPath }) {
|
|
295
|
+
const isFix = changeId.startsWith('FIX-');
|
|
296
|
+
const primaryHeading = isFix ? '## Root Cause Contract' : '## Contract Summary';
|
|
297
|
+
const primaryFragment = isFix ? 'root-cause-contract' : 'contract-summary';
|
|
298
|
+
const tasksText = await readText(tasksPath, '');
|
|
299
|
+
|
|
300
|
+
if (markdownHasHeading(tasksText, primaryHeading)) {
|
|
301
|
+
return {
|
|
302
|
+
path: tasksPath,
|
|
303
|
+
fragment: primaryFragment,
|
|
304
|
+
usesTaskContract: true
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const preferredContractPaths = isFix
|
|
309
|
+
? [analysisPath, designPath]
|
|
310
|
+
: [designPath, analysisPath];
|
|
311
|
+
const legacyPath = (await Promise.all(
|
|
312
|
+
preferredContractPaths.map(async (candidate) => ((await exists(candidate)) ? candidate : null))
|
|
313
|
+
)).find(Boolean);
|
|
314
|
+
const legacyText = legacyPath ? await readText(legacyPath, '') : '';
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
path: legacyPath,
|
|
318
|
+
fragment: resolveLegacyContractFragment(legacyText),
|
|
319
|
+
usesTaskContract: false
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function deriveWorkflowNextAction({ progress, nextTask, report, reportPath, missingVerificationCommands }) {
|
|
324
|
+
if (missingVerificationCommands) {
|
|
325
|
+
return {
|
|
326
|
+
skill: 'cc-plan',
|
|
327
|
+
action: 'repair-task-verification',
|
|
328
|
+
taskId: nextTask.id,
|
|
329
|
+
reason: 'current task has no executable verification command',
|
|
330
|
+
mustOpen: ['planning/task-manifest.json']
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (nextTask) {
|
|
335
|
+
const status = nextTask.status || 'pending';
|
|
336
|
+
return {
|
|
337
|
+
skill: 'cc-do',
|
|
338
|
+
action: status === 'pending' ? 'execute-current-task' : 'resume-current-task',
|
|
339
|
+
taskId: nextTask.id,
|
|
340
|
+
reason: status === 'pending'
|
|
341
|
+
? 'a dependency-ready task is still pending'
|
|
342
|
+
: `current task ${nextTask.id} is ${status} and should be resumed before selecting new work`
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (progress.totalTasks > 0 && progress.completedTasks === progress.totalTasks) {
|
|
347
|
+
const readiness = report ? deriveShipReadiness(report, { reportPath }) : null;
|
|
348
|
+
if (readiness?.ready) {
|
|
349
|
+
return {
|
|
350
|
+
skill: 'cc-act',
|
|
351
|
+
action: 'ship-or-handoff',
|
|
352
|
+
reason: 'all tasks are complete and ship-readiness is ready'
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return {
|
|
357
|
+
skill: 'cc-check',
|
|
358
|
+
action: 'build-fresh-verdict',
|
|
359
|
+
reason: report
|
|
360
|
+
? 'all tasks are complete but ship-readiness is blocked'
|
|
361
|
+
: 'all tasks are complete but no report card exists',
|
|
362
|
+
blockers: readiness?.blockers || ['missing report-card']
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return {
|
|
367
|
+
skill: 'cc-plan',
|
|
368
|
+
action: 'repair-task-graph',
|
|
369
|
+
reason: 'no ready task can be selected from the current manifest'
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function getWorkflowContextArtifactRefs(repoRoot, changeId, options = {}) {
|
|
374
|
+
const change = getChangePaths(repoRoot, changeId, options);
|
|
375
|
+
return [
|
|
376
|
+
getTasksMarkdownPath(repoRoot, changeId, options),
|
|
377
|
+
path.join(change.planningDir, 'design.md'),
|
|
378
|
+
path.join(change.planningDir, 'analysis.md'),
|
|
379
|
+
getTaskManifestPath(repoRoot, changeId, options),
|
|
380
|
+
path.join(change.changeDir, 'change-meta.json'),
|
|
381
|
+
getRuntimeStatePath(repoRoot, changeId, options),
|
|
382
|
+
getReportCardPath(repoRoot, changeId, options)
|
|
383
|
+
];
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function getWorkflowContextRequiredArtifactRefs(repoRoot, changeId, options = {}) {
|
|
387
|
+
return [
|
|
388
|
+
getTaskManifestPath(repoRoot, changeId, options)
|
|
389
|
+
];
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
async function getWorkflowContext(repoRoot, changeId, options = {}) {
|
|
393
|
+
const change = getChangePaths(repoRoot, changeId, options);
|
|
394
|
+
const manifestPath = getTaskManifestPath(repoRoot, changeId, options);
|
|
395
|
+
const reportPath = getReportCardPath(repoRoot, changeId, options);
|
|
396
|
+
const statePath = getRuntimeStatePath(repoRoot, changeId, options);
|
|
397
|
+
const tasksPath = getTasksMarkdownPath(repoRoot, changeId, options);
|
|
398
|
+
const changeMetaPath = path.join(change.changeDir, 'change-meta.json');
|
|
399
|
+
const designPath = path.join(change.planningDir, 'design.md');
|
|
400
|
+
const analysisPath = path.join(change.planningDir, 'analysis.md');
|
|
401
|
+
const contractResolution = await resolveCanonicalContractPath({
|
|
402
|
+
changeId,
|
|
403
|
+
tasksPath,
|
|
404
|
+
designPath,
|
|
405
|
+
analysisPath
|
|
406
|
+
});
|
|
407
|
+
const canonicalContractPath = contractResolution.path;
|
|
408
|
+
|
|
409
|
+
const [manifest, report, state, changeMeta] = await Promise.all([
|
|
410
|
+
readWorkflowArtifact(manifestPath),
|
|
411
|
+
readWorkflowArtifact(reportPath, { required: false }),
|
|
412
|
+
readWorkflowArtifact(statePath, { required: false }),
|
|
413
|
+
readWorkflowArtifact(changeMetaPath, { required: false })
|
|
414
|
+
]);
|
|
415
|
+
const progress = deriveTaskProgress(manifest.tasks || []);
|
|
416
|
+
const queues = deriveTaskQueues(manifest);
|
|
417
|
+
const nextTask = selectNextTask(manifest, queues);
|
|
418
|
+
const relativeContractPath = canonicalContractPath ? relativePath(repoRoot, canonicalContractPath) : null;
|
|
419
|
+
const relativeDesignPath = await maybeRelativePath(repoRoot, designPath);
|
|
420
|
+
const relativeAnalysisPath = await maybeRelativePath(repoRoot, analysisPath);
|
|
421
|
+
const relativeManifestPath = relativePath(repoRoot, manifestPath);
|
|
422
|
+
const relativeTasksPath = await maybeRelativePath(repoRoot, tasksPath);
|
|
423
|
+
const relativeChangeMetaPath = await maybeRelativePath(repoRoot, changeMetaPath);
|
|
424
|
+
const relativeStatePath = await maybeRelativePath(repoRoot, statePath);
|
|
425
|
+
const relativeReportPath = await maybeRelativePath(repoRoot, reportPath);
|
|
426
|
+
const relativePrimaryContractRef = relativeContractPath && contractResolution.fragment
|
|
427
|
+
? withFragment(relativeContractPath, contractResolution.fragment)
|
|
428
|
+
: relativeContractPath;
|
|
429
|
+
const relativeContractRef = relativeContractPath && contractResolution.fragment
|
|
430
|
+
? withFragment(relativeContractPath, contractResolution.fragment)
|
|
431
|
+
: relativeContractPath;
|
|
432
|
+
const relativeContractIndexRef = contractResolution.usesTaskContract && relativeContractPath
|
|
433
|
+
? withFragment(relativeContractPath, 'progressive-disclosure-index')
|
|
434
|
+
: relativeContractRef;
|
|
435
|
+
const refs = {
|
|
436
|
+
repoRoot,
|
|
437
|
+
relativeContractPath,
|
|
438
|
+
relativeContractRef,
|
|
439
|
+
relativeContractIndexRef,
|
|
440
|
+
relativePrimaryContractRef,
|
|
441
|
+
relativeDesignPath,
|
|
442
|
+
relativeAnalysisPath,
|
|
443
|
+
relativeTasksPath,
|
|
444
|
+
relativeManifestPath,
|
|
445
|
+
relativeChangeMetaPath,
|
|
446
|
+
relativeStatePath,
|
|
447
|
+
relativeReportPath
|
|
448
|
+
};
|
|
449
|
+
const queryCommand = [
|
|
450
|
+
'cc-devflow query workflow-context',
|
|
451
|
+
`--change ${changeId}`,
|
|
452
|
+
options.changeKey ? `--change-key ${change.changeKey}` : '',
|
|
453
|
+
'--cwd <repo-root>'
|
|
454
|
+
].filter(Boolean).join(' ');
|
|
455
|
+
const taskReadFiles = normalizeTaskReadFiles(nextTask?.context?.readFiles || [], refs);
|
|
456
|
+
const taskDeepOpen = taskReadFiles.filter((filePath) => !isArtifactReadFile(filePath, refs));
|
|
457
|
+
const source = await buildSourceIndex({
|
|
458
|
+
manifestPath,
|
|
459
|
+
canonicalContractPath,
|
|
460
|
+
tasksPath,
|
|
461
|
+
changeMetaPath,
|
|
462
|
+
reportPath,
|
|
463
|
+
refs
|
|
464
|
+
});
|
|
465
|
+
const defaultOpen = await validateOpenRefs(
|
|
466
|
+
buildDefaultOpenRefs({ nextTask, refs, source }),
|
|
467
|
+
{ repoRoot }
|
|
468
|
+
);
|
|
469
|
+
const taskCommands = deriveCommandsToTrust({ manifest, nextTask, progress });
|
|
470
|
+
const missingVerificationCommands = Boolean(nextTask && taskCommands.length === 0);
|
|
471
|
+
const currentTaskSummary = summarizeTask(nextTask);
|
|
472
|
+
const mustNotForget = await buildMustNotForget({
|
|
473
|
+
manifest,
|
|
474
|
+
nextTask,
|
|
475
|
+
report,
|
|
476
|
+
source,
|
|
477
|
+
refs,
|
|
478
|
+
canonicalContractPath,
|
|
479
|
+
missingVerificationCommands
|
|
480
|
+
});
|
|
481
|
+
const packetOnly = await buildPacketOnly({
|
|
482
|
+
canonicalContractPath,
|
|
483
|
+
manifest,
|
|
484
|
+
currentTaskSummary,
|
|
485
|
+
nextTask,
|
|
486
|
+
report,
|
|
487
|
+
mustNotForget
|
|
488
|
+
});
|
|
489
|
+
const deepOpen = await validateDeepOpenGroups([
|
|
490
|
+
deepOpenGroup({
|
|
491
|
+
when: 'scope_or_contract_uncertain',
|
|
492
|
+
conditions: [
|
|
493
|
+
'confidence.scope < 0.85',
|
|
494
|
+
"task.type in ['ARCH', 'SECURITY', 'DATA_MIGRATION']",
|
|
495
|
+
'user_request_changes_scope == true',
|
|
496
|
+
'do_not_redecide_missing == true'
|
|
497
|
+
],
|
|
498
|
+
refs: [
|
|
499
|
+
{ ref: relativeContractPath, reason: 'full contract for scope and do-not-redecide disputes' },
|
|
500
|
+
{ ref: withFragment(relativeContractPath, 'approved-direction'), reason: 'approved direction section' },
|
|
501
|
+
{ ref: relativeTasksPath, reason: 'task protocol and handoff context' },
|
|
502
|
+
{ ref: withFragment(relativeChangeMetaPath, '/risk'), reason: 'risk and spec sync context' }
|
|
503
|
+
].filter((entry) => entry.ref),
|
|
504
|
+
source
|
|
505
|
+
}),
|
|
506
|
+
deepOpenGroup({
|
|
507
|
+
when: 'task_or_dependency_uncertain',
|
|
508
|
+
conditions: [
|
|
509
|
+
'currentTask == null',
|
|
510
|
+
'dependency_state_unclear == true',
|
|
511
|
+
'active_phase_unclear == true'
|
|
512
|
+
],
|
|
513
|
+
refs: [
|
|
514
|
+
{ ref: withFragment(relativeManifestPath, nextTask ? `/tasks/${nextTask.id}` : '/summary'), reason: 'task graph source of truth' },
|
|
515
|
+
{ ref: withFragment(relativeTasksPath, nextTask ? `task.${nextTask.id}` : 'execution-handoff'), reason: 'human task block and execution protocol' }
|
|
516
|
+
],
|
|
517
|
+
source,
|
|
518
|
+
command: queryCommand
|
|
519
|
+
}),
|
|
520
|
+
deepOpenGroup({
|
|
521
|
+
when: 'parallel_or_touch_ownership_uncertain',
|
|
522
|
+
conditions: [
|
|
523
|
+
'task.touches.length > 3',
|
|
524
|
+
'parallel_candidates.length > 1',
|
|
525
|
+
'current_file_not_listed_in_task_files == true',
|
|
526
|
+
'touch_paths_overlap == true'
|
|
527
|
+
],
|
|
528
|
+
refs: [
|
|
529
|
+
{ ref: relativeTasksPath, reason: 'task ownership and slicing details' },
|
|
530
|
+
{ ref: relativeManifestPath, reason: 'machine task graph and touched files' }
|
|
531
|
+
],
|
|
532
|
+
source,
|
|
533
|
+
command: `bash .claude/skills/cc-do/scripts/select-ready-tasks.sh --manifest ${relativeManifestPath}`
|
|
534
|
+
}),
|
|
535
|
+
deepOpenGroup({
|
|
536
|
+
when: 'verification_or_recovery_needed',
|
|
537
|
+
conditions: [
|
|
538
|
+
'verification_failed == true',
|
|
539
|
+
'missingVerificationCommands == true',
|
|
540
|
+
'report.reroute != none',
|
|
541
|
+
'runtime_log_needed == true'
|
|
542
|
+
],
|
|
543
|
+
refs: [
|
|
544
|
+
{ ref: relativeTasksPath, reason: 'current task status and completion marks' },
|
|
545
|
+
{ ref: relativeReportPath, reason: 'latest review gate verdict' },
|
|
546
|
+
{ ref: relativeManifestPath, reason: 'verification command source' }
|
|
547
|
+
].filter((entry) => entry.ref),
|
|
548
|
+
source
|
|
549
|
+
}),
|
|
550
|
+
deepOpenGroup({
|
|
551
|
+
when: 'implementation_details_needed',
|
|
552
|
+
conditions: [
|
|
553
|
+
'agent_needs_code_context == true',
|
|
554
|
+
'current_file_not_listed_in_task_files == true'
|
|
555
|
+
],
|
|
556
|
+
refs: taskDeepOpen.map((ref) => ({ ref, reason: 'selected task implementation context' })),
|
|
557
|
+
source
|
|
558
|
+
}),
|
|
559
|
+
deepOpenGroup({
|
|
560
|
+
when: 'delivery_or_ship_readiness_needed',
|
|
561
|
+
conditions: [
|
|
562
|
+
'progress.completedTasks == progress.totalTasks',
|
|
563
|
+
'nextAction.skill == cc-act',
|
|
564
|
+
'ship_mode_unclear == true'
|
|
565
|
+
],
|
|
566
|
+
refs: [
|
|
567
|
+
{ ref: relativeReportPath, reason: 'ship-readiness verdict input' },
|
|
568
|
+
{ ref: 'devflow/changes/<change-key>/handoff/', reason: 'delivery handoff artifacts' }
|
|
569
|
+
],
|
|
570
|
+
source,
|
|
571
|
+
command: `cc-devflow query ship-readiness --change ${changeId}${options.changeKey ? ` --change-key ${change.changeKey}` : ''} --cwd <repo-root>`
|
|
572
|
+
})
|
|
573
|
+
], { repoRoot });
|
|
574
|
+
const nextAction = deriveWorkflowNextAction({
|
|
575
|
+
progress,
|
|
576
|
+
nextTask,
|
|
577
|
+
report,
|
|
578
|
+
reportPath,
|
|
579
|
+
missingVerificationCommands
|
|
580
|
+
});
|
|
581
|
+
const sourceHashes = buildSourceHashes(source);
|
|
582
|
+
|
|
583
|
+
return {
|
|
584
|
+
schemaVersion: 'workflow-context.v2',
|
|
585
|
+
changeId: manifest.changeId || changeId,
|
|
586
|
+
changeKey: change.changeKey,
|
|
587
|
+
generatedAt: new Date().toISOString(),
|
|
588
|
+
source,
|
|
589
|
+
legacyFallback: contractResolution.usesTaskContract ? false : Boolean(canonicalContractPath),
|
|
590
|
+
goal: manifest.goal || state?.goal || '',
|
|
591
|
+
route: changeId.startsWith('FIX-') ? 'IDCA' : 'PDCA',
|
|
592
|
+
lifecycle: {
|
|
593
|
+
stage: state ? deriveLifecycleStage({
|
|
594
|
+
state,
|
|
595
|
+
manifest,
|
|
596
|
+
report,
|
|
597
|
+
hasPrBrief: await exists(getIntentPrBriefPath(repoRoot, changeId, options))
|
|
598
|
+
}) : null,
|
|
599
|
+
status: state?.status || null,
|
|
600
|
+
approval: state ? getApprovalState(state, manifest) : null
|
|
601
|
+
},
|
|
602
|
+
progress,
|
|
603
|
+
nextAction,
|
|
604
|
+
currentTask: currentTaskSummary,
|
|
605
|
+
queues,
|
|
606
|
+
progressiveDisclosure: {
|
|
607
|
+
mode: 'compact-first',
|
|
608
|
+
rule: 'Use this as a context index: packetOnly routes, mustNotForget guards judgment, source artifacts decide disputed facts. If you need to guess, open the referenced source first.',
|
|
609
|
+
contextBudget: {
|
|
610
|
+
packetFields: Object.keys(packetOnly).length,
|
|
611
|
+
defaultRefs: defaultOpen.length,
|
|
612
|
+
defaultCommands: taskCommands.length,
|
|
613
|
+
deepOpenGroups: deepOpen.length,
|
|
614
|
+
deepOpeners: 6
|
|
615
|
+
},
|
|
616
|
+
packetOnly,
|
|
617
|
+
mustNotForget,
|
|
618
|
+
sourceHashes,
|
|
619
|
+
stalenessCheck: {
|
|
620
|
+
policy: 'if any source hash differs from sourceHashes, rerun workflow-context before acting',
|
|
621
|
+
command: queryCommand
|
|
622
|
+
},
|
|
623
|
+
defaultOpen,
|
|
624
|
+
deepOpen,
|
|
625
|
+
commandsToTrust: taskCommands,
|
|
626
|
+
missingVerificationCommands,
|
|
627
|
+
manifestIssue: missingVerificationCommands
|
|
628
|
+
? 'current task has no executable verification command'
|
|
629
|
+
: null,
|
|
630
|
+
failClosed: [
|
|
631
|
+
'If manifest, contract, change-meta, report-card, tasks, or Git state changed after packet generation, rerun workflow-context.',
|
|
632
|
+
'If current task status is running, in_progress, active, blocked, needs_fix, or failed, resume it before selecting another ready task.',
|
|
633
|
+
'If verification command is missing, route to cc-plan repair-task-verification instead of cc-do.',
|
|
634
|
+
'If scope, ownership, dependency, or source confidence is uncertain, open the source artifact before acting.'
|
|
635
|
+
],
|
|
636
|
+
uncertaintyPolicy: {
|
|
637
|
+
mode: 'open-source-before-acting',
|
|
638
|
+
openOn: [
|
|
639
|
+
'scope uncertainty',
|
|
640
|
+
'task ambiguity',
|
|
641
|
+
'dependency ambiguity',
|
|
642
|
+
'missing verification',
|
|
643
|
+
'changed files outside manifest',
|
|
644
|
+
'test failure',
|
|
645
|
+
'user changes requirement',
|
|
646
|
+
'security or data migration impact'
|
|
647
|
+
]
|
|
648
|
+
},
|
|
649
|
+
openWhen: await Promise.all([
|
|
650
|
+
{
|
|
651
|
+
trigger: 'the current task, dependency state, or active phase is unclear',
|
|
652
|
+
conditions: [
|
|
653
|
+
'currentTask == null',
|
|
654
|
+
'dependency_state_unclear == true',
|
|
655
|
+
'active_phase_unclear == true'
|
|
656
|
+
],
|
|
657
|
+
open: dedupeOpenRefs([
|
|
658
|
+
openRef(nextTask ? withFragment(relativeManifestPath, `/tasks/${nextTask.id}`) : withFragment(relativeManifestPath, '/summary'), 'task graph source of truth', source),
|
|
659
|
+
openRef(withFragment(relativeTasksPath, nextTask ? `task.${nextTask.id}` : 'execution-handoff'), 'task block and execution handoff', source)
|
|
660
|
+
]),
|
|
661
|
+
command: queryCommand
|
|
662
|
+
},
|
|
663
|
+
{
|
|
664
|
+
trigger: 'scope, design, root cause, or do-not-redecide contract is disputed',
|
|
665
|
+
conditions: [
|
|
666
|
+
'confidence.scope < 0.85',
|
|
667
|
+
'user_request_changes_scope == true',
|
|
668
|
+
'do_not_redecide_missing == true'
|
|
669
|
+
],
|
|
670
|
+
open: dedupeOpenRefs([
|
|
671
|
+
openRef(relativeContractPath, 'full contract for scope and do-not-redecide disputes', source),
|
|
672
|
+
openRef(relativeTasksPath, 'task protocol and handoff context', source),
|
|
673
|
+
openRef(withFragment(relativeChangeMetaPath, '/risk'), 'risk and spec sync context', source)
|
|
674
|
+
])
|
|
675
|
+
},
|
|
676
|
+
{
|
|
677
|
+
trigger: 'parallel dispatch, touched-path ownership, or task slicing is disputed',
|
|
678
|
+
conditions: [
|
|
679
|
+
'parallel_candidates.length > 1',
|
|
680
|
+
'touch_paths_overlap == true',
|
|
681
|
+
'current_file_not_listed_in_task_files == true'
|
|
682
|
+
],
|
|
683
|
+
open: dedupeOpenRefs([
|
|
684
|
+
openRef(relativeTasksPath, 'task ownership and slicing details', source),
|
|
685
|
+
openRef(relativeManifestPath, 'machine task graph and touched files', source)
|
|
686
|
+
]),
|
|
687
|
+
command: `bash .claude/skills/cc-do/scripts/select-ready-tasks.sh --manifest ${relativeManifestPath}`
|
|
688
|
+
},
|
|
689
|
+
{
|
|
690
|
+
trigger: 'task evidence, review gates, or recovery state is needed',
|
|
691
|
+
conditions: [
|
|
692
|
+
'verification_failed == true',
|
|
693
|
+
'report.reroute != none',
|
|
694
|
+
'runtime_log_needed == true'
|
|
695
|
+
],
|
|
696
|
+
open: dedupeOpenRefs([
|
|
697
|
+
openRef(relativeTasksPath, 'current task status and completion marks', source),
|
|
698
|
+
openRef(relativeReportPath, 'latest review gate verdict', source),
|
|
699
|
+
openRef(relativeManifestPath, 'verification command source', source)
|
|
700
|
+
])
|
|
701
|
+
},
|
|
702
|
+
{
|
|
703
|
+
trigger: 'implementation file details are needed for the selected task',
|
|
704
|
+
conditions: [
|
|
705
|
+
'agent_needs_code_context == true',
|
|
706
|
+
'current_file_not_listed_in_task_files == true'
|
|
707
|
+
],
|
|
708
|
+
open: dedupeOpenRefs(taskDeepOpen.map((ref) => openRef(ref, 'selected task implementation context', source)))
|
|
709
|
+
},
|
|
710
|
+
{
|
|
711
|
+
trigger: 'all tasks are complete and delivery can start',
|
|
712
|
+
conditions: [
|
|
713
|
+
'progress.completedTasks == progress.totalTasks',
|
|
714
|
+
'nextAction.skill == cc-act',
|
|
715
|
+
'ship_mode_unclear == true'
|
|
716
|
+
],
|
|
717
|
+
open: dedupeOpenRefs([
|
|
718
|
+
openRef(relativeReportPath, 'ship-readiness verdict input', source),
|
|
719
|
+
openRef('devflow/changes/<change-key>/handoff/', 'delivery handoff artifacts', source)
|
|
720
|
+
]),
|
|
721
|
+
command: `cc-devflow query ship-readiness --change ${changeId}${options.changeKey ? ` --change-key ${change.changeKey}` : ''} --cwd <repo-root>`
|
|
722
|
+
}
|
|
723
|
+
].map(async (entry) => ({
|
|
724
|
+
...entry,
|
|
725
|
+
open: await validateOpenRefs(entry.open, { repoRoot })
|
|
726
|
+
})))
|
|
727
|
+
},
|
|
728
|
+
artifactRefs: dedupe([
|
|
729
|
+
relativeContractPath,
|
|
730
|
+
relativeTasksPath,
|
|
731
|
+
relativeManifestPath,
|
|
732
|
+
relativeChangeMetaPath,
|
|
733
|
+
relativeReportPath
|
|
734
|
+
]),
|
|
735
|
+
planningMeta: {
|
|
736
|
+
planVersion: manifest.metadata?.planVersion || null,
|
|
737
|
+
reqPlanSkillVersion: manifest.planningMeta?.reqPlanSkillVersion || null,
|
|
738
|
+
sourceCapability: changeMeta?.spec?.primaryCapability || null,
|
|
739
|
+
specSyncStatus: changeMeta?.spec?.syncStatus || null
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
module.exports = {
|
|
745
|
+
getWorkflowContext,
|
|
746
|
+
getWorkflowContextArtifactRefs,
|
|
747
|
+
getWorkflowContextRequiredArtifactRefs
|
|
748
|
+
};
|