cc-devflow 4.5.10 → 4.5.12
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 +23 -0
- package/.claude/skills/cc-act/PLAYBOOK.md +17 -269
- package/.claude/skills/cc-act/SKILL.md +38 -418
- package/.claude/skills/cc-act/assets/PROJECT_POSTMORTEM_INDEX_TEMPLATE.md +2 -13
- package/.claude/skills/cc-act/assets/PROJECT_POSTMORTEM_TEMPLATE.md +1 -9
- package/.claude/skills/cc-act/assets/PR_BRIEF_TEMPLATE.md +21 -177
- package/.claude/skills/cc-act/references/closure-contract.md +12 -63
- package/.claude/skills/cc-act/references/git-commit-guidelines.md +5 -5
- package/.claude/skills/cc-act/scripts/cc-act-common.sh +5 -322
- package/.claude/skills/cc-act/scripts/detect-ship-target.sh +11 -2
- package/.claude/skills/cc-act/scripts/inspect-git-index.sh +58 -0
- package/.claude/skills/cc-act/scripts/render-pr-brief.sh +40 -440
- package/.claude/skills/cc-act/scripts/verify-act-gate.sh +10 -50
- package/.claude/skills/cc-check/CHANGELOG.md +24 -0
- package/.claude/skills/cc-check/PLAYBOOK.md +19 -273
- package/.claude/skills/cc-check/SKILL.md +33 -454
- package/.claude/skills/cc-check/references/review-contract.md +12 -147
- package/.claude/skills/cc-dev/CHANGELOG.md +20 -0
- package/.claude/skills/cc-dev/PLAYBOOK.md +1 -1
- package/.claude/skills/cc-dev/SKILL.md +52 -130
- package/.claude/skills/cc-dev/scripts/resolve-cc-devflow.sh +181 -0
- package/.claude/skills/cc-do/CHANGELOG.md +17 -0
- package/.claude/skills/cc-do/PLAYBOOK.md +19 -113
- package/.claude/skills/cc-do/SKILL.md +39 -236
- package/.claude/skills/cc-do/references/execution-recovery.md +15 -109
- package/.claude/skills/cc-do/scripts/cc-do-common.sh +5 -57
- package/.claude/skills/cc-do/scripts/check-task-status.sh +35 -65
- package/.claude/skills/cc-do/scripts/mark-task-complete.sh +9 -46
- package/.claude/skills/cc-do/scripts/select-ready-tasks.sh +29 -97
- package/.claude/skills/cc-investigate/CHANGELOG.md +23 -0
- package/.claude/skills/cc-investigate/PLAYBOOK.md +20 -180
- package/.claude/skills/cc-investigate/SKILL.md +65 -513
- package/.claude/skills/cc-investigate/assets/TASKS_TEMPLATE.md +48 -95
- package/.claude/skills/cc-investigate/references/investigation-contract.md +14 -217
- package/.claude/skills/cc-next/CHANGELOG.md +6 -0
- package/.claude/skills/cc-next/PLAYBOOK.md +12 -8
- package/.claude/skills/cc-next/SKILL.md +34 -140
- package/.claude/skills/cc-plan/CHANGELOG.md +29 -0
- package/.claude/skills/cc-plan/PLAYBOOK.md +22 -161
- package/.claude/skills/cc-plan/SKILL.md +47 -640
- package/.claude/skills/cc-plan/assets/TASKS_TEMPLATE.md +30 -225
- package/.claude/skills/cc-plan/references/planning-contract.md +24 -160
- package/.claude/skills/cc-plan/scripts/next-change-key.sh +8 -44
- package/.claude/skills/cc-plan/scripts/parse-task-dependencies.js +2 -2
- package/.claude/skills/cc-plan/scripts/validate-scope.sh +1 -1
- package/.claude/skills/cc-pr-land/SKILL.md +14 -114
- package/.claude/skills/cc-pr-review/CHANGELOG.md +4 -0
- package/.claude/skills/cc-pr-review/SKILL.md +20 -103
- package/.claude/skills/cc-review/CHANGELOG.md +17 -0
- package/.claude/skills/cc-review/PLAYBOOK.md +13 -86
- package/.claude/skills/cc-review/SKILL.md +53 -241
- package/.claude/skills/cc-review/references/e2e-and-plugin-verification.md +2 -2
- package/.claude/skills/cc-review/references/implementation-review-branch.md +7 -147
- package/.claude/skills/cc-review/references/plan-review-branch.md +5 -147
- package/.claude/skills/cc-review/references/review-methods.md +10 -218
- package/.claude/skills/cc-review/scripts/collect-review-context.sh +4 -63
- package/.claude/skills/cc-roadmap/PLAYBOOK.md +1 -1
- package/.claude/skills/cc-roadmap/SKILL.md +3 -3
- package/.claude/skills/cc-simplify/CHANGELOG.md +7 -0
- package/.claude/skills/cc-simplify/SKILL.md +26 -21
- package/.claude/skills/cc-spec-init/PLAYBOOK.md +12 -48
- package/.claude/skills/cc-spec-init/SKILL.md +29 -132
- package/.claude/skills/cc-spec-init/references/spec-contract.md +8 -17
- package/CHANGELOG.md +27 -0
- package/README.md +5 -3
- package/README.zh-CN.md +5 -3
- package/bin/cc-devflow-cli.js +20 -260
- package/bin/cc-devflow.js +44 -7
- package/docs/commands/README.md +1 -1
- package/docs/commands/README.zh-CN.md +1 -1
- package/docs/examples/README.md +1 -1
- package/docs/examples/START-HERE.md +14 -14
- package/docs/examples/example-bindings.json +11 -11
- package/docs/examples/full-design-blocked/README.md +4 -6
- package/docs/examples/full-design-blocked/changes/REQ-002-bulk-invite-import/{planning/tasks.md → task.md} +20 -15
- package/docs/examples/local-handoff/README.md +8 -11
- package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/handoff/pr-brief.md +31 -0
- package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/{planning/tasks.md → task.md} +18 -13
- package/docs/examples/pdca-loop/README.md +6 -9
- package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/handoff/pr-brief.md +9 -11
- package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/{planning/tasks.md → task.md} +18 -13
- package/docs/examples/scripts/check-example-bindings.sh +11 -62
- package/docs/guides/artifact-contract.md +10 -36
- package/docs/guides/getting-started.md +8 -7
- package/docs/guides/getting-started.zh-CN.md +8 -7
- package/docs/guides/minimize-artifacts.md +16 -116
- package/docs/guides/project-postmortem.md +14 -71
- package/lib/compiler/__tests__/skills-registry.test.js +9 -8
- package/lib/compiler/resource-copier.js +29 -0
- package/lib/skill-runtime/__tests__/archive-change.test.js +2 -2
- package/lib/skill-runtime/__tests__/benchmark-skills.test.js +109 -0
- package/lib/skill-runtime/__tests__/cli-bootstrap.integration.test.js +14 -4
- package/lib/skill-runtime/errors.js +3 -3
- package/lib/skill-runtime/index.js +5 -23
- package/lib/skill-runtime/paths.js +5 -52
- package/lib/skill-runtime/query-registry.js +4 -4
- package/lib/skill-runtime/query.js +89 -201
- package/lib/skill-runtime/store.js +4 -40
- package/lib/skill-runtime/trace.js +2 -2
- package/package.json +5 -7
- package/.claude/skills/cc-act/assets/PROJECT_POSTMORTEM_PRINCIPLES_TEMPLATE.md +0 -29
- package/.claude/skills/cc-act/assets/RELEASE_NOTE_TEMPLATE.md +0 -54
- package/.claude/skills/cc-act/scripts/generate-status-report.sh +0 -92
- package/.claude/skills/cc-act/scripts/sync-act-docs.sh +0 -355
- package/.claude/skills/cc-check/assets/REPORT_CARD_TEMPLATE.json +0 -234
- package/.claude/skills/cc-check/scripts/render-report-card.js +0 -438
- package/.claude/skills/cc-check/scripts/verify-gate.sh +0 -85
- package/.claude/skills/cc-do/scripts/build-task-context.sh +0 -175
- package/.claude/skills/cc-do/scripts/record-review-decision.sh +0 -88
- package/.claude/skills/cc-do/scripts/recover-workflow.sh +0 -82
- package/.claude/skills/cc-do/scripts/run-problem-analysis.sh +0 -70
- package/.claude/skills/cc-do/scripts/verify-task-gates.sh +0 -109
- package/.claude/skills/cc-do/scripts/write-task-checkpoint.sh +0 -92
- package/.claude/skills/cc-investigate/assets/TASK_MANIFEST_TEMPLATE.json +0 -225
- package/.claude/skills/cc-plan/assets/TASK_MANIFEST_TEMPLATE.json +0 -179
- package/.claude/skills/cc-spec-init/assets/CHANGE_META_TEMPLATE.json +0 -28
- package/.claude/skills/cc-spec-init/scripts/validate-spec-links.sh +0 -45
- package/docs/examples/full-design-blocked/changes/REQ-002-bulk-invite-import/planning/design.md +0 -234
- package/docs/examples/full-design-blocked/changes/REQ-002-bulk-invite-import/planning/task-manifest.json +0 -488
- package/docs/examples/full-design-blocked/changes/REQ-002-bulk-invite-import/review/report-card.json +0 -189
- package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/handoff/resume-index.md +0 -39
- package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/handoff/status.md +0 -29
- package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/planning/design.md +0 -123
- package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/planning/task-manifest.json +0 -292
- package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/review/report-card.json +0 -136
- package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/handoff/status.md +0 -29
- package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/planning/design.md +0 -124
- package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/planning/task-manifest.json +0 -292
- package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/review/report-card.json +0 -136
- package/docs/get-shit-done-strategy-audit.md +0 -518
- package/docs/skill-runtime-migration.md +0 -46
- package/lib/skill-runtime/__tests__/approve.test.js +0 -92
- package/lib/skill-runtime/__tests__/autopilot.test.js +0 -253
- package/lib/skill-runtime/__tests__/benchmark-artifacts.test.js +0 -165
- package/lib/skill-runtime/__tests__/delegation.test.js +0 -97
- package/lib/skill-runtime/__tests__/dispatch.test.js +0 -237
- package/lib/skill-runtime/__tests__/intent.test.js +0 -203
- package/lib/skill-runtime/__tests__/lifecycle.test.js +0 -169
- package/lib/skill-runtime/__tests__/planner.tdd.test.js +0 -331
- package/lib/skill-runtime/__tests__/prepare-pr.test.js +0 -126
- package/lib/skill-runtime/__tests__/query.test.js +0 -860
- package/lib/skill-runtime/__tests__/readiness.test.js +0 -53
- package/lib/skill-runtime/__tests__/release.test.js +0 -85
- package/lib/skill-runtime/__tests__/review-check-integration.test.js +0 -148
- package/lib/skill-runtime/__tests__/review-records.test.js +0 -619
- package/lib/skill-runtime/__tests__/runtime.integration.test.js +0 -351
- package/lib/skill-runtime/__tests__/schemas.test.js +0 -337
- package/lib/skill-runtime/__tests__/task-contract-migrate.test.js +0 -137
- package/lib/skill-runtime/__tests__/task-contract.test.js +0 -783
- package/lib/skill-runtime/__tests__/team-state.test.js +0 -51
- package/lib/skill-runtime/__tests__/verify-artifacts.test.js +0 -203
- package/lib/skill-runtime/__tests__/worker-run.test.js +0 -275
- package/lib/skill-runtime/__tests__/worker.test.js +0 -56
- package/lib/skill-runtime/__tests__/workflow-context-legacy-fallback.test.js +0 -31
- package/lib/skill-runtime/__tests__/workflow-context.test.js +0 -98
- package/lib/skill-runtime/artifacts.js +0 -88
- package/lib/skill-runtime/context-index.js +0 -545
- package/lib/skill-runtime/delegation.js +0 -533
- package/lib/skill-runtime/intent.js +0 -309
- package/lib/skill-runtime/lifecycle.js +0 -294
- package/lib/skill-runtime/operations/CLAUDE.md +0 -19
- package/lib/skill-runtime/operations/approve.js +0 -81
- package/lib/skill-runtime/operations/autopilot-core.js +0 -337
- package/lib/skill-runtime/operations/autopilot-execution.js +0 -307
- package/lib/skill-runtime/operations/autopilot-shared.js +0 -48
- package/lib/skill-runtime/operations/autopilot.js +0 -163
- package/lib/skill-runtime/operations/dispatch.js +0 -416
- package/lib/skill-runtime/operations/init.js +0 -60
- package/lib/skill-runtime/operations/janitor.js +0 -61
- package/lib/skill-runtime/operations/plan.js +0 -59
- package/lib/skill-runtime/operations/prepare-pr.js +0 -25
- package/lib/skill-runtime/operations/release.js +0 -99
- package/lib/skill-runtime/operations/resume.js +0 -126
- package/lib/skill-runtime/operations/review-records.js +0 -265
- package/lib/skill-runtime/operations/snapshot.js +0 -45
- package/lib/skill-runtime/operations/task-contract.js +0 -524
- package/lib/skill-runtime/operations/verify.js +0 -170
- package/lib/skill-runtime/operations/worker-run.js +0 -531
- package/lib/skill-runtime/operations/worker.js +0 -33
- package/lib/skill-runtime/planner.js +0 -539
- package/lib/skill-runtime/readiness.js +0 -84
- package/lib/skill-runtime/review-records.js +0 -123
- package/lib/skill-runtime/review.js +0 -855
- package/lib/skill-runtime/schemas.js +0 -746
- package/lib/skill-runtime/task-contract.js +0 -187
- package/lib/skill-runtime/team-state.js +0 -122
- package/lib/skill-runtime/workflow-context.js +0 -748
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* [INPUT]: 依赖 autopilot shared/core/execution runner,接收 changeId、goal、from、resume 等推进参数。
|
|
3
|
-
* [OUTPUT]: 作为兼容入口串联最小阶段闭环,写入 autopilot 起止日志,并返回当前阶段、已执行步骤、resume 索引与 PR brief 路径。
|
|
4
|
-
* [POS]: skill runtime 的 autopilot 兼容入口,只保留阶段顺序与总结果汇总。
|
|
5
|
-
* [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const {
|
|
9
|
-
exists
|
|
10
|
-
} = require('../store');
|
|
11
|
-
const {
|
|
12
|
-
getIntentResumeIndexPath
|
|
13
|
-
} = require('../artifacts');
|
|
14
|
-
const { syncIntentMemory } = require('../intent');
|
|
15
|
-
const {
|
|
16
|
-
normalizeStage,
|
|
17
|
-
stageIndex,
|
|
18
|
-
resolveCompletedStage,
|
|
19
|
-
runDiscoverStage,
|
|
20
|
-
runConvergeStage,
|
|
21
|
-
runApprovalStage,
|
|
22
|
-
runDelegateStage,
|
|
23
|
-
runFinishStages
|
|
24
|
-
} = require('./autopilot-core');
|
|
25
|
-
const {
|
|
26
|
-
loadState
|
|
27
|
-
} = require('./autopilot-shared');
|
|
28
|
-
const {
|
|
29
|
-
runExecutionStage
|
|
30
|
-
} = require('./autopilot-execution');
|
|
31
|
-
|
|
32
|
-
async function runAutopilot({
|
|
33
|
-
repoRoot,
|
|
34
|
-
changeId,
|
|
35
|
-
goal,
|
|
36
|
-
from,
|
|
37
|
-
resume = false,
|
|
38
|
-
overwrite = false,
|
|
39
|
-
parallel = 3,
|
|
40
|
-
maxRetries,
|
|
41
|
-
strict = false,
|
|
42
|
-
skipReview = false,
|
|
43
|
-
release = false,
|
|
44
|
-
workerProvider,
|
|
45
|
-
workerProviderArgs,
|
|
46
|
-
workerCommand
|
|
47
|
-
}) {
|
|
48
|
-
const executed = [];
|
|
49
|
-
const fromIndex = stageIndex(from);
|
|
50
|
-
const resolveResumeIndexPath = async () => (
|
|
51
|
-
await exists(getIntentResumeIndexPath(repoRoot, changeId))
|
|
52
|
-
? getIntentResumeIndexPath(repoRoot, changeId)
|
|
53
|
-
: null
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
await syncIntentMemory(repoRoot, changeId, {
|
|
57
|
-
event: 'autopilot_started',
|
|
58
|
-
reason: `Autopilot entered at stage ${normalizeStage(from) || 'discover'}`
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
let snapshot = await loadState(repoRoot, changeId);
|
|
62
|
-
|
|
63
|
-
snapshot = await runDiscoverStage({
|
|
64
|
-
repoRoot,
|
|
65
|
-
changeId,
|
|
66
|
-
goal,
|
|
67
|
-
fromIndex,
|
|
68
|
-
executed,
|
|
69
|
-
snapshot
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
snapshot = await runConvergeStage({
|
|
73
|
-
repoRoot,
|
|
74
|
-
changeId,
|
|
75
|
-
goal,
|
|
76
|
-
fromIndex,
|
|
77
|
-
overwrite,
|
|
78
|
-
executed,
|
|
79
|
-
snapshot
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
const approvalResult = await runApprovalStage({
|
|
83
|
-
repoRoot,
|
|
84
|
-
changeId,
|
|
85
|
-
fromIndex,
|
|
86
|
-
executed,
|
|
87
|
-
snapshot
|
|
88
|
-
});
|
|
89
|
-
snapshot = approvalResult.snapshot;
|
|
90
|
-
|
|
91
|
-
if (approvalResult.shouldStop) {
|
|
92
|
-
const completedStage = resolveCompletedStage(snapshot);
|
|
93
|
-
await syncIntentMemory(repoRoot, changeId, {
|
|
94
|
-
event: 'autopilot_finished',
|
|
95
|
-
reason: `Autopilot stopped at stage ${completedStage}`
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
return {
|
|
99
|
-
changeId,
|
|
100
|
-
executed,
|
|
101
|
-
currentStage: completedStage,
|
|
102
|
-
lifecycleStatus: snapshot.state?.status || 'unknown',
|
|
103
|
-
reportOverall: snapshot.report?.overall || null,
|
|
104
|
-
resumeIndexPath: await resolveResumeIndexPath(),
|
|
105
|
-
prBriefPath: null
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
snapshot = await runDelegateStage({
|
|
110
|
-
repoRoot,
|
|
111
|
-
changeId,
|
|
112
|
-
fromIndex,
|
|
113
|
-
executed,
|
|
114
|
-
snapshot
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
snapshot = await runExecutionStage({
|
|
118
|
-
repoRoot,
|
|
119
|
-
changeId,
|
|
120
|
-
fromIndex,
|
|
121
|
-
snapshot,
|
|
122
|
-
executed,
|
|
123
|
-
parallel,
|
|
124
|
-
maxRetries,
|
|
125
|
-
resume,
|
|
126
|
-
workerProvider,
|
|
127
|
-
workerProviderArgs,
|
|
128
|
-
workerCommand
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
const finishResult = await runFinishStages({
|
|
132
|
-
repoRoot,
|
|
133
|
-
changeId,
|
|
134
|
-
from,
|
|
135
|
-
fromIndex,
|
|
136
|
-
strict,
|
|
137
|
-
skipReview,
|
|
138
|
-
release,
|
|
139
|
-
executed,
|
|
140
|
-
snapshot
|
|
141
|
-
});
|
|
142
|
-
snapshot = finishResult.snapshot;
|
|
143
|
-
|
|
144
|
-
const completedStage = resolveCompletedStage(snapshot);
|
|
145
|
-
await syncIntentMemory(repoRoot, changeId, {
|
|
146
|
-
event: 'autopilot_finished',
|
|
147
|
-
reason: `Autopilot stopped at stage ${completedStage}`
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
return {
|
|
151
|
-
changeId,
|
|
152
|
-
executed,
|
|
153
|
-
currentStage: completedStage,
|
|
154
|
-
lifecycleStatus: snapshot.state?.status || 'unknown',
|
|
155
|
-
reportOverall: snapshot.report?.overall || null,
|
|
156
|
-
resumeIndexPath: await resolveResumeIndexPath(),
|
|
157
|
-
prBriefPath: finishResult.prBriefPath
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
module.exports = {
|
|
162
|
-
runAutopilot
|
|
163
|
-
};
|
|
@@ -1,416 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* [INPUT]: 依赖 manifest/events 与 shell 执行能力,接收并行度与重试参数。
|
|
3
|
-
* [OUTPUT]: 更新 task-manifest 状态,失败或 debug 时写入每任务 events.jsonl。
|
|
4
|
-
* [POS]: skill runtime Stage-4 执行入口,被内部执行与恢复链路复用。
|
|
5
|
-
* [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const {
|
|
9
|
-
nowIso,
|
|
10
|
-
appendJsonl,
|
|
11
|
-
writeJson,
|
|
12
|
-
readJson,
|
|
13
|
-
runCommand,
|
|
14
|
-
getTaskManifestPath,
|
|
15
|
-
getRuntimeStatePath,
|
|
16
|
-
getEventsPath
|
|
17
|
-
} = require('../store');
|
|
18
|
-
const { parseManifest } = require('../schemas');
|
|
19
|
-
const { applyManifestExecutionState } = require('../planner');
|
|
20
|
-
const { syncIntentMemory } = require('../intent');
|
|
21
|
-
const {
|
|
22
|
-
summarizeTaskStates,
|
|
23
|
-
classifyDelegationMode,
|
|
24
|
-
getApprovalState,
|
|
25
|
-
isExecutionApproved
|
|
26
|
-
} = require('../lifecycle');
|
|
27
|
-
const {
|
|
28
|
-
findAssignment,
|
|
29
|
-
ensureWorkerWorkspace,
|
|
30
|
-
updateAssignmentStatus,
|
|
31
|
-
updateWorkerState,
|
|
32
|
-
appendWorkerJournal,
|
|
33
|
-
appendMessageBus
|
|
34
|
-
} = require('../delegation');
|
|
35
|
-
|
|
36
|
-
function toSessionId(taskId) {
|
|
37
|
-
return `${taskId}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function dependenciesPassed(task, taskMap) {
|
|
41
|
-
return task.dependsOn.every((depId) => {
|
|
42
|
-
const dep = taskMap.get(depId);
|
|
43
|
-
return dep && dep.status === 'passed';
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function dependenciesFailed(task, taskMap) {
|
|
48
|
-
return task.dependsOn.some((depId) => {
|
|
49
|
-
const dep = taskMap.get(depId);
|
|
50
|
-
return dep && (dep.status === 'failed' || dep.status === 'skipped');
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function hasTouchConflict(task, lockedTouches) {
|
|
55
|
-
if (!task.touches || task.touches.length === 0) {
|
|
56
|
-
return false;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return task.touches.some((filePath) => lockedTouches.has(filePath));
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function selectBatch(readyTasks, parallel) {
|
|
63
|
-
const selected = [];
|
|
64
|
-
const lockedTouches = new Set();
|
|
65
|
-
|
|
66
|
-
for (const task of readyTasks) {
|
|
67
|
-
if (selected.length >= parallel) {
|
|
68
|
-
break;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (hasTouchConflict(task, lockedTouches)) {
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
selected.push(task);
|
|
76
|
-
for (const touched of task.touches) {
|
|
77
|
-
lockedTouches.add(touched);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return selected;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
async function writeEvent(repoRoot, changeId, taskId, event, debug = false) {
|
|
85
|
-
if (!debug && !String(event.type || '').includes('failed') && !String(event.type || '').includes('rejected')) {
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
const eventsPath = getEventsPath(repoRoot, changeId, taskId);
|
|
89
|
-
await appendJsonl(eventsPath, event);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async function writeManifest(repoRoot, changeId, manifest) {
|
|
93
|
-
const manifestPath = getTaskManifestPath(repoRoot, changeId);
|
|
94
|
-
applyManifestExecutionState(manifest, nowIso());
|
|
95
|
-
await writeJson(manifestPath, manifest);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
async function executeTask({ repoRoot, changeId, task, retryOverride, debug = false }) {
|
|
99
|
-
const assignment = await findAssignment(repoRoot, changeId, task.id);
|
|
100
|
-
const worker = await ensureWorkerWorkspace(repoRoot, changeId, assignment);
|
|
101
|
-
const sessionId = toSessionId(task.id);
|
|
102
|
-
const commands = [...task.run, ...task.checks];
|
|
103
|
-
const taskRetry = Number.isInteger(retryOverride) ? retryOverride : task.maxRetries;
|
|
104
|
-
const maxAttempts = Math.max(1, taskRetry + 1);
|
|
105
|
-
const expectedPlanVersion = task.planVersion || 1;
|
|
106
|
-
|
|
107
|
-
if (assignment) {
|
|
108
|
-
await updateAssignmentStatus(repoRoot, changeId, task.id, {
|
|
109
|
-
status: 'running',
|
|
110
|
-
workspace: worker.workspacePath,
|
|
111
|
-
sessionId,
|
|
112
|
-
summary: worker.mode === 'worktree'
|
|
113
|
-
? `Worker ${assignment.workerId} running in worktree`
|
|
114
|
-
: `Worker ${assignment.workerId} fell back to controller workspace: ${worker.reason}`
|
|
115
|
-
});
|
|
116
|
-
await updateWorkerState(repoRoot, changeId, assignment.workerId, {
|
|
117
|
-
role: assignment.role,
|
|
118
|
-
planVersion: expectedPlanVersion,
|
|
119
|
-
status: 'running',
|
|
120
|
-
currentTask: task.id,
|
|
121
|
-
workspace: worker.workspacePath
|
|
122
|
-
});
|
|
123
|
-
await appendWorkerJournal(
|
|
124
|
-
repoRoot,
|
|
125
|
-
changeId,
|
|
126
|
-
assignment.workerId,
|
|
127
|
-
`started ${task.id} in ${worker.mode === 'worktree' ? worker.workspacePath : 'controller workspace'}`
|
|
128
|
-
);
|
|
129
|
-
await appendMessageBus(
|
|
130
|
-
repoRoot,
|
|
131
|
-
changeId,
|
|
132
|
-
`${assignment.workerId} started ${task.id} in ${worker.mode === 'worktree' ? worker.workspacePath : 'controller workspace'}`
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
while (task.attempts < maxAttempts) {
|
|
137
|
-
task.attempts += 1;
|
|
138
|
-
task.status = 'running';
|
|
139
|
-
|
|
140
|
-
await writeEvent(repoRoot, changeId, task.id, {
|
|
141
|
-
type: 'task_started',
|
|
142
|
-
changeId,
|
|
143
|
-
taskId: task.id,
|
|
144
|
-
sessionId,
|
|
145
|
-
attempt: task.attempts,
|
|
146
|
-
timestamp: nowIso()
|
|
147
|
-
}, debug);
|
|
148
|
-
|
|
149
|
-
let failed = false;
|
|
150
|
-
let failureMessage = '';
|
|
151
|
-
|
|
152
|
-
for (const command of commands) {
|
|
153
|
-
const result = await runCommand(command, {
|
|
154
|
-
cwd: worker.cwd,
|
|
155
|
-
timeoutMs: 30 * 60 * 1000
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
await writeEvent(repoRoot, changeId, task.id, {
|
|
159
|
-
type: 'command_finished',
|
|
160
|
-
changeId,
|
|
161
|
-
taskId: task.id,
|
|
162
|
-
sessionId,
|
|
163
|
-
attempt: task.attempts,
|
|
164
|
-
command,
|
|
165
|
-
code: result.code,
|
|
166
|
-
durationMs: result.durationMs,
|
|
167
|
-
timestamp: nowIso()
|
|
168
|
-
}, debug);
|
|
169
|
-
|
|
170
|
-
if (result.code !== 0) {
|
|
171
|
-
failed = true;
|
|
172
|
-
failureMessage = (result.stderr || result.stdout || 'Command failed').trim();
|
|
173
|
-
break;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if (!failed) {
|
|
178
|
-
const latestManifest = parseManifest(await readJson(getTaskManifestPath(repoRoot, changeId)));
|
|
179
|
-
const latestPlanVersion = latestManifest.metadata?.planVersion || 1;
|
|
180
|
-
|
|
181
|
-
if (latestPlanVersion !== expectedPlanVersion) {
|
|
182
|
-
failed = true;
|
|
183
|
-
failureMessage = `Stale result rejected: task ran with plan_version ${expectedPlanVersion} but current plan_version is ${latestPlanVersion}`;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if (!failed) {
|
|
188
|
-
task.status = 'passed';
|
|
189
|
-
task.lastError = undefined;
|
|
190
|
-
|
|
191
|
-
await writeEvent(repoRoot, changeId, task.id, {
|
|
192
|
-
type: 'task_passed',
|
|
193
|
-
changeId,
|
|
194
|
-
taskId: task.id,
|
|
195
|
-
sessionId,
|
|
196
|
-
attempts: task.attempts,
|
|
197
|
-
timestamp: nowIso()
|
|
198
|
-
}, debug);
|
|
199
|
-
|
|
200
|
-
if (assignment) {
|
|
201
|
-
await updateAssignmentStatus(repoRoot, changeId, task.id, {
|
|
202
|
-
status: 'completed',
|
|
203
|
-
workspace: worker.workspacePath,
|
|
204
|
-
sessionId,
|
|
205
|
-
summary: `Worker ${assignment.workerId} completed task successfully`
|
|
206
|
-
});
|
|
207
|
-
await updateWorkerState(repoRoot, changeId, assignment.workerId, {
|
|
208
|
-
role: assignment.role,
|
|
209
|
-
planVersion: expectedPlanVersion,
|
|
210
|
-
status: 'idle',
|
|
211
|
-
currentTask: 'none',
|
|
212
|
-
workspace: worker.workspacePath
|
|
213
|
-
});
|
|
214
|
-
await appendWorkerJournal(repoRoot, changeId, assignment.workerId, `completed ${task.id}`);
|
|
215
|
-
await appendMessageBus(repoRoot, changeId, `${assignment.workerId} completed ${task.id}`);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
task.status = 'failed';
|
|
222
|
-
task.lastError = failureMessage;
|
|
223
|
-
|
|
224
|
-
await writeEvent(repoRoot, changeId, task.id, {
|
|
225
|
-
type: failureMessage.includes('Stale result rejected') ? 'task_stale_rejected' : 'task_failed',
|
|
226
|
-
changeId,
|
|
227
|
-
taskId: task.id,
|
|
228
|
-
sessionId,
|
|
229
|
-
attempt: task.attempts,
|
|
230
|
-
error: failureMessage,
|
|
231
|
-
timestamp: nowIso()
|
|
232
|
-
}, debug);
|
|
233
|
-
|
|
234
|
-
if (assignment) {
|
|
235
|
-
await updateAssignmentStatus(repoRoot, changeId, task.id, {
|
|
236
|
-
status: 'failed',
|
|
237
|
-
workspace: worker.workspacePath,
|
|
238
|
-
sessionId,
|
|
239
|
-
summary: failureMessage.slice(0, 240)
|
|
240
|
-
});
|
|
241
|
-
await updateWorkerState(repoRoot, changeId, assignment.workerId, {
|
|
242
|
-
role: assignment.role,
|
|
243
|
-
planVersion: expectedPlanVersion,
|
|
244
|
-
status: 'idle',
|
|
245
|
-
currentTask: 'none',
|
|
246
|
-
workspace: worker.workspacePath
|
|
247
|
-
});
|
|
248
|
-
await appendWorkerJournal(
|
|
249
|
-
repoRoot,
|
|
250
|
-
changeId,
|
|
251
|
-
assignment.workerId,
|
|
252
|
-
`failed ${task.id}: ${failureMessage.slice(0, 120)}`
|
|
253
|
-
);
|
|
254
|
-
await appendMessageBus(repoRoot, changeId, `${assignment.workerId} failed ${task.id}: ${failureMessage.slice(0, 120)}`);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function isTaskIncluded(task, executionScope, state) {
|
|
260
|
-
if (executionScope === 'direct') {
|
|
261
|
-
return classifyDelegationMode(task, state || {}) === 'direct';
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
return true;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
async function runDispatch({
|
|
268
|
-
repoRoot,
|
|
269
|
-
changeId,
|
|
270
|
-
parallel = 3,
|
|
271
|
-
maxRetries,
|
|
272
|
-
resume = false,
|
|
273
|
-
executionScope = 'all',
|
|
274
|
-
debug = false
|
|
275
|
-
}) {
|
|
276
|
-
const manifestPath = getTaskManifestPath(repoRoot, changeId);
|
|
277
|
-
const manifest = parseManifest(await readJson(manifestPath));
|
|
278
|
-
const activePlanVersion = manifest.metadata?.planVersion || 1;
|
|
279
|
-
|
|
280
|
-
// Update change-state.json to in_progress on first dispatch
|
|
281
|
-
const statePath = getRuntimeStatePath(repoRoot, changeId);
|
|
282
|
-
const state = await readJson(statePath, null);
|
|
283
|
-
const stateExists = require('fs').existsSync(statePath);
|
|
284
|
-
const approval = getApprovalState(state, manifest);
|
|
285
|
-
if (!isExecutionApproved(state, manifest)) {
|
|
286
|
-
await syncIntentMemory(repoRoot, changeId, {
|
|
287
|
-
event: 'dispatch_blocked_unapproved',
|
|
288
|
-
reason: `Plan version ${approval.planVersion || activePlanVersion} must be approved before execute`
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
return {
|
|
292
|
-
changeId,
|
|
293
|
-
manifestPath,
|
|
294
|
-
summary: summarizeTaskStates(manifest.tasks),
|
|
295
|
-
success: false,
|
|
296
|
-
reason: `Execution blocked until plan_version ${approval.planVersion || activePlanVersion} is approved. Return to the cc-plan approval gate and keep execution mode ${approval.executionMode}.`
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
if (stateExists) {
|
|
301
|
-
if (state.status === 'planned' || state.status === 'initialized') {
|
|
302
|
-
state.status = 'in_progress';
|
|
303
|
-
state.updatedAt = nowIso();
|
|
304
|
-
await writeJson(statePath, state);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
if (resume) {
|
|
309
|
-
for (const task of manifest.tasks) {
|
|
310
|
-
if (task.status === 'running') {
|
|
311
|
-
task.status = 'pending';
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
for (const task of manifest.tasks) {
|
|
317
|
-
task.planVersion = activePlanVersion;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
await syncIntentMemory(repoRoot, changeId, {
|
|
321
|
-
event: resume ? 'resume_started' : 'dispatch_started',
|
|
322
|
-
reason: resume ? 'Resume flow entered dispatch loop' : 'Dispatch flow entered execution loop'
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
const safeParallel = Math.max(1, Number.parseInt(parallel, 10) || 1);
|
|
326
|
-
|
|
327
|
-
while (true) {
|
|
328
|
-
const taskMap = new Map(manifest.tasks.map((task) => [task.id, task]));
|
|
329
|
-
const pending = manifest.tasks.filter((task) =>
|
|
330
|
-
task.status === 'pending' && isTaskIncluded(task, executionScope, state)
|
|
331
|
-
);
|
|
332
|
-
|
|
333
|
-
if (pending.length === 0) {
|
|
334
|
-
break;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
for (const task of pending) {
|
|
338
|
-
if (dependenciesFailed(task, taskMap)) {
|
|
339
|
-
task.status = 'skipped';
|
|
340
|
-
task.lastError = 'Blocked by failed dependency';
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
const ready = manifest.tasks.filter(
|
|
345
|
-
(task) => task.status === 'pending' &&
|
|
346
|
-
isTaskIncluded(task, executionScope, state) &&
|
|
347
|
-
dependenciesPassed(task, taskMap)
|
|
348
|
-
);
|
|
349
|
-
|
|
350
|
-
if (ready.length === 0) {
|
|
351
|
-
await writeManifest(repoRoot, changeId, manifest);
|
|
352
|
-
|
|
353
|
-
await syncIntentMemory(repoRoot, changeId, {
|
|
354
|
-
event: 'dispatch_blocked',
|
|
355
|
-
reason: 'No ready tasks left after dependency evaluation'
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
return {
|
|
359
|
-
changeId,
|
|
360
|
-
manifestPath,
|
|
361
|
-
summary: summarizeTaskStates(manifest.tasks),
|
|
362
|
-
success: false,
|
|
363
|
-
reason: 'No ready tasks left. Check dependencies and failed tasks.'
|
|
364
|
-
};
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
const batch = selectBatch(ready, safeParallel);
|
|
368
|
-
|
|
369
|
-
if (batch.length === 0) {
|
|
370
|
-
const firstReady = ready[0];
|
|
371
|
-
batch.push(firstReady);
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
await Promise.all(
|
|
375
|
-
batch.map((task) =>
|
|
376
|
-
executeTask({
|
|
377
|
-
repoRoot,
|
|
378
|
-
changeId,
|
|
379
|
-
task,
|
|
380
|
-
retryOverride: Number.isInteger(maxRetries) ? maxRetries : undefined,
|
|
381
|
-
debug
|
|
382
|
-
})
|
|
383
|
-
)
|
|
384
|
-
);
|
|
385
|
-
|
|
386
|
-
await writeManifest(repoRoot, changeId, manifest);
|
|
387
|
-
await syncIntentMemory(repoRoot, changeId, {
|
|
388
|
-
event: 'dispatch_batch_completed',
|
|
389
|
-
reason: `Completed batch of ${batch.length} task(s)`
|
|
390
|
-
});
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
await writeManifest(repoRoot, changeId, manifest);
|
|
394
|
-
|
|
395
|
-
const summary = summarizeTaskStates(manifest.tasks);
|
|
396
|
-
const scopedPending = manifest.tasks.filter((task) =>
|
|
397
|
-
['pending', 'running', 'failed'].includes(task.status) && isTaskIncluded(task, executionScope, state)
|
|
398
|
-
);
|
|
399
|
-
const success = scopedPending.length === 0;
|
|
400
|
-
|
|
401
|
-
await syncIntentMemory(repoRoot, changeId, {
|
|
402
|
-
event: success ? 'dispatch_completed' : 'dispatch_incomplete',
|
|
403
|
-
reason: success ? 'All executable tasks completed' : 'Dispatch loop ended with unresolved tasks'
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
return {
|
|
407
|
-
changeId,
|
|
408
|
-
manifestPath,
|
|
409
|
-
summary,
|
|
410
|
-
success
|
|
411
|
-
};
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
module.exports = {
|
|
415
|
-
runDispatch
|
|
416
|
-
};
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* [INPUT]: 依赖 store 提供 repo/change 路径与读写能力,接收 changeId/goal 初始化参数。
|
|
3
|
-
* [OUTPUT]: 写入 canonical devflow 目录与 change-state.json,返回初始化摘要。
|
|
4
|
-
* [POS]: skill runtime Stage-1 初始化入口,供内部初始化链路复用。
|
|
5
|
-
* [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const {
|
|
9
|
-
nowIso,
|
|
10
|
-
ensureDir,
|
|
11
|
-
writeJson,
|
|
12
|
-
readJson,
|
|
13
|
-
getChangeDir,
|
|
14
|
-
getRuntimeStatePath
|
|
15
|
-
} = require('../store');
|
|
16
|
-
const { getChangePaths, getChangeSlug } = require('../paths');
|
|
17
|
-
|
|
18
|
-
async function runInit({ repoRoot, changeId, goal }) {
|
|
19
|
-
const changePaths = getChangePaths(repoRoot, changeId, { goal });
|
|
20
|
-
const changeDir = getChangeDir(repoRoot, changeId, { goal });
|
|
21
|
-
const statePath = getRuntimeStatePath(repoRoot, changeId, { goal });
|
|
22
|
-
|
|
23
|
-
await ensureDir(changeDir);
|
|
24
|
-
await ensureDir(changePaths.metaDir);
|
|
25
|
-
await ensureDir(changePaths.planningDir);
|
|
26
|
-
await ensureDir(changePaths.reviewDir);
|
|
27
|
-
await ensureDir(changePaths.handoffDir);
|
|
28
|
-
|
|
29
|
-
const previous = (await readJson(statePath, {})) || {};
|
|
30
|
-
const createdAt = previous.createdAt || nowIso();
|
|
31
|
-
const nextState = {
|
|
32
|
-
changeId,
|
|
33
|
-
changeKey: changePaths.changeKey,
|
|
34
|
-
slug: getChangeSlug(changeId, changePaths.changeKey),
|
|
35
|
-
createdAt,
|
|
36
|
-
goal: goal || previous.goal || `Deliver ${changeId} safely with task-state truth.`,
|
|
37
|
-
status: 'initialized',
|
|
38
|
-
initializedAt: previous.initializedAt || nowIso(),
|
|
39
|
-
approval: {
|
|
40
|
-
status: 'pending',
|
|
41
|
-
executionMode: previous.approval?.executionMode || 'delegate'
|
|
42
|
-
},
|
|
43
|
-
updatedAt: nowIso()
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
await writeJson(statePath, nextState);
|
|
47
|
-
|
|
48
|
-
return {
|
|
49
|
-
changeId,
|
|
50
|
-
changeDir,
|
|
51
|
-
runtimeDir: changePaths.executionDir,
|
|
52
|
-
changeKey: changePaths.changeKey,
|
|
53
|
-
statePath,
|
|
54
|
-
status: nextState.status
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
module.exports = {
|
|
59
|
-
runInit
|
|
60
|
-
};
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* [INPUT]: 依赖 devflow/changes 下 execution/tasks 目录与 mtime,接收保留小时阈值。
|
|
3
|
-
* [OUTPUT]: 删除过期任务运行态目录,并输出清理统计。
|
|
4
|
-
* [POS]: skill runtime 熵清理入口,被 CLI `harness:janitor` 调用。
|
|
5
|
-
* [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const fs = require('fs');
|
|
9
|
-
const path = require('path');
|
|
10
|
-
const {
|
|
11
|
-
listDirectories,
|
|
12
|
-
getRuntimeRoot
|
|
13
|
-
} = require('../store');
|
|
14
|
-
|
|
15
|
-
async function removeDirectoryRecursive(dirPath) {
|
|
16
|
-
await fs.promises.rm(dirPath, { recursive: true, force: true });
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
async function runJanitor({ repoRoot, hours = 72 }) {
|
|
20
|
-
const runtimeRoot = getRuntimeRoot(repoRoot);
|
|
21
|
-
const cutoffMs = Date.now() - Number(hours) * 60 * 60 * 1000;
|
|
22
|
-
|
|
23
|
-
const changeDirs = await listDirectories(runtimeRoot);
|
|
24
|
-
let removedTaskDirs = 0;
|
|
25
|
-
let removedChangeDirs = 0;
|
|
26
|
-
|
|
27
|
-
for (const changeDir of changeDirs) {
|
|
28
|
-
const taskDirs = await listDirectories(path.join(changeDir, 'execution', 'tasks'));
|
|
29
|
-
|
|
30
|
-
for (const taskDir of taskDirs) {
|
|
31
|
-
const stat = await fs.promises.stat(taskDir);
|
|
32
|
-
const isStale = stat.mtimeMs < cutoffMs;
|
|
33
|
-
if (!isStale) {
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
await removeDirectoryRecursive(taskDir);
|
|
38
|
-
removedTaskDirs += 1;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const remaining = await listDirectories(path.join(changeDir, 'execution', 'tasks'));
|
|
42
|
-
if (remaining.length === 0) {
|
|
43
|
-
const stat = await fs.promises.stat(changeDir);
|
|
44
|
-
if (stat.mtimeMs < cutoffMs) {
|
|
45
|
-
await removeDirectoryRecursive(path.join(changeDir, 'execution', 'tasks'));
|
|
46
|
-
removedChangeDirs += 1;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return {
|
|
52
|
-
runtimeRoot,
|
|
53
|
-
removedTaskDirs,
|
|
54
|
-
removedChangeDirs,
|
|
55
|
-
cutoffHours: Number(hours)
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
module.exports = {
|
|
60
|
-
runJanitor
|
|
61
|
-
};
|