cclaw-cli 7.7.1 → 8.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +210 -134
- package/dist/artifact-frontmatter.d.ts +51 -0
- package/dist/artifact-frontmatter.js +131 -0
- package/dist/artifact-paths.d.ts +7 -27
- package/dist/artifact-paths.js +20 -249
- package/dist/cancel.d.ts +16 -0
- package/dist/cancel.js +66 -0
- package/dist/cli.d.ts +2 -27
- package/dist/cli.js +90 -508
- package/dist/compound.d.ts +26 -0
- package/dist/compound.js +96 -0
- package/dist/config.d.ts +14 -51
- package/dist/config.js +23 -359
- package/dist/constants.d.ts +11 -18
- package/dist/constants.js +19 -106
- package/dist/content/antipatterns.d.ts +1 -0
- package/dist/content/antipatterns.js +109 -0
- package/dist/content/artifact-templates.d.ts +10 -0
- package/dist/content/artifact-templates.js +550 -0
- package/dist/content/cancel-command.d.ts +2 -2
- package/dist/content/cancel-command.js +25 -17
- package/dist/content/core-agents.d.ts +9 -233
- package/dist/content/core-agents.js +39 -768
- package/dist/content/decision-protocol.d.ts +1 -12
- package/dist/content/decision-protocol.js +27 -20
- package/dist/content/examples.d.ts +8 -42
- package/dist/content/examples.js +293 -425
- package/dist/content/idea-command.d.ts +2 -0
- package/dist/content/idea-command.js +38 -0
- package/dist/content/iron-laws.d.ts +4 -138
- package/dist/content/iron-laws.js +18 -197
- package/dist/content/meta-skill.d.ts +1 -3
- package/dist/content/meta-skill.js +57 -134
- package/dist/content/node-hooks.d.ts +12 -8
- package/dist/content/node-hooks.js +188 -838
- package/dist/content/recovery.d.ts +8 -0
- package/dist/content/recovery.js +179 -0
- package/dist/content/reference-patterns.d.ts +4 -13
- package/dist/content/reference-patterns.js +260 -389
- package/dist/content/research-playbooks.d.ts +8 -8
- package/dist/content/research-playbooks.js +108 -121
- package/dist/content/review-loop.d.ts +6 -192
- package/dist/content/review-loop.js +29 -731
- package/dist/content/skills.d.ts +8 -38
- package/dist/content/skills.js +681 -732
- package/dist/content/specialist-prompts/architect.d.ts +1 -0
- package/dist/content/specialist-prompts/architect.js +225 -0
- package/dist/content/specialist-prompts/brainstormer.d.ts +1 -0
- package/dist/content/specialist-prompts/brainstormer.js +168 -0
- package/dist/content/specialist-prompts/index.d.ts +2 -0
- package/dist/content/specialist-prompts/index.js +14 -0
- package/dist/content/specialist-prompts/planner.d.ts +1 -0
- package/dist/content/specialist-prompts/planner.js +182 -0
- package/dist/content/specialist-prompts/reviewer.d.ts +1 -0
- package/dist/content/specialist-prompts/reviewer.js +193 -0
- package/dist/content/specialist-prompts/security-reviewer.d.ts +1 -0
- package/dist/content/specialist-prompts/security-reviewer.js +133 -0
- package/dist/content/specialist-prompts/slice-builder.d.ts +1 -0
- package/dist/content/specialist-prompts/slice-builder.js +232 -0
- package/dist/content/stage-playbooks.d.ts +8 -0
- package/dist/content/stage-playbooks.js +404 -0
- package/dist/content/start-command.d.ts +2 -12
- package/dist/content/start-command.js +221 -207
- package/dist/flow-state.d.ts +21 -178
- package/dist/flow-state.js +67 -170
- package/dist/fs-utils.d.ts +6 -26
- package/dist/fs-utils.js +29 -162
- package/dist/gitignore.d.ts +2 -1
- package/dist/gitignore.js +51 -34
- package/dist/harness-detect.d.ts +10 -0
- package/dist/harness-detect.js +29 -0
- package/dist/install.d.ts +27 -15
- package/dist/install.js +230 -1342
- package/dist/knowledge-store.d.ts +19 -163
- package/dist/knowledge-store.js +56 -590
- package/dist/logger.d.ts +8 -3
- package/dist/logger.js +13 -4
- package/dist/orchestrator-routing.d.ts +29 -0
- package/dist/orchestrator-routing.js +156 -0
- package/dist/run-persistence.d.ts +7 -118
- package/dist/run-persistence.js +29 -845
- package/dist/runtime/run-hook.entry.d.ts +1 -3
- package/dist/runtime/run-hook.entry.js +19 -4
- package/dist/runtime/run-hook.mjs +13 -1024
- package/dist/types.d.ts +25 -261
- package/dist/types.js +8 -36
- package/package.json +6 -3
- package/dist/artifact-linter/brainstorm.d.ts +0 -2
- package/dist/artifact-linter/brainstorm.js +0 -353
- package/dist/artifact-linter/design.d.ts +0 -18
- package/dist/artifact-linter/design.js +0 -444
- package/dist/artifact-linter/findings-dedup.d.ts +0 -56
- package/dist/artifact-linter/findings-dedup.js +0 -232
- package/dist/artifact-linter/plan.d.ts +0 -2
- package/dist/artifact-linter/plan.js +0 -826
- package/dist/artifact-linter/review-army.d.ts +0 -49
- package/dist/artifact-linter/review-army.js +0 -520
- package/dist/artifact-linter/review.d.ts +0 -2
- package/dist/artifact-linter/review.js +0 -113
- package/dist/artifact-linter/scope.d.ts +0 -2
- package/dist/artifact-linter/scope.js +0 -158
- package/dist/artifact-linter/shared.d.ts +0 -637
- package/dist/artifact-linter/shared.js +0 -2163
- package/dist/artifact-linter/ship.d.ts +0 -2
- package/dist/artifact-linter/ship.js +0 -250
- package/dist/artifact-linter/spec.d.ts +0 -2
- package/dist/artifact-linter/spec.js +0 -176
- package/dist/artifact-linter/tdd.d.ts +0 -118
- package/dist/artifact-linter/tdd.js +0 -1404
- package/dist/artifact-linter.d.ts +0 -15
- package/dist/artifact-linter.js +0 -517
- package/dist/codex-feature-flag.d.ts +0 -58
- package/dist/codex-feature-flag.js +0 -193
- package/dist/content/closeout-guidance.d.ts +0 -14
- package/dist/content/closeout-guidance.js +0 -44
- package/dist/content/diff-command.d.ts +0 -1
- package/dist/content/diff-command.js +0 -43
- package/dist/content/harness-doc.d.ts +0 -1
- package/dist/content/harness-doc.js +0 -65
- package/dist/content/hook-events.d.ts +0 -9
- package/dist/content/hook-events.js +0 -23
- package/dist/content/hook-manifest.d.ts +0 -81
- package/dist/content/hook-manifest.js +0 -156
- package/dist/content/hooks.d.ts +0 -11
- package/dist/content/hooks.js +0 -1972
- package/dist/content/idea.d.ts +0 -60
- package/dist/content/idea.js +0 -416
- package/dist/content/language-policy.d.ts +0 -2
- package/dist/content/language-policy.js +0 -13
- package/dist/content/learnings.d.ts +0 -6
- package/dist/content/learnings.js +0 -141
- package/dist/content/observe.d.ts +0 -19
- package/dist/content/observe.js +0 -86
- package/dist/content/opencode-plugin.d.ts +0 -1
- package/dist/content/opencode-plugin.js +0 -635
- package/dist/content/review-prompts.d.ts +0 -1
- package/dist/content/review-prompts.js +0 -104
- package/dist/content/runtime-shared-snippets.d.ts +0 -8
- package/dist/content/runtime-shared-snippets.js +0 -80
- package/dist/content/session-hooks.d.ts +0 -7
- package/dist/content/session-hooks.js +0 -107
- package/dist/content/skills-elicitation.d.ts +0 -1
- package/dist/content/skills-elicitation.js +0 -167
- package/dist/content/stage-command.d.ts +0 -2
- package/dist/content/stage-command.js +0 -17
- package/dist/content/stage-schema.d.ts +0 -117
- package/dist/content/stage-schema.js +0 -955
- package/dist/content/stages/_lint-metadata/index.d.ts +0 -2
- package/dist/content/stages/_lint-metadata/index.js +0 -97
- package/dist/content/stages/brainstorm.d.ts +0 -2
- package/dist/content/stages/brainstorm.js +0 -184
- package/dist/content/stages/design.d.ts +0 -2
- package/dist/content/stages/design.js +0 -288
- package/dist/content/stages/index.d.ts +0 -8
- package/dist/content/stages/index.js +0 -11
- package/dist/content/stages/plan.d.ts +0 -2
- package/dist/content/stages/plan.js +0 -191
- package/dist/content/stages/review.d.ts +0 -2
- package/dist/content/stages/review.js +0 -240
- package/dist/content/stages/schema-types.d.ts +0 -203
- package/dist/content/stages/schema-types.js +0 -1
- package/dist/content/stages/scope.d.ts +0 -2
- package/dist/content/stages/scope.js +0 -254
- package/dist/content/stages/ship.d.ts +0 -2
- package/dist/content/stages/ship.js +0 -159
- package/dist/content/stages/spec.d.ts +0 -2
- package/dist/content/stages/spec.js +0 -170
- package/dist/content/stages/tdd.d.ts +0 -4
- package/dist/content/stages/tdd.js +0 -273
- package/dist/content/state-contracts.d.ts +0 -1
- package/dist/content/state-contracts.js +0 -63
- package/dist/content/status-command.d.ts +0 -4
- package/dist/content/status-command.js +0 -109
- package/dist/content/subagent-context-skills.d.ts +0 -4
- package/dist/content/subagent-context-skills.js +0 -279
- package/dist/content/subagents.d.ts +0 -3
- package/dist/content/subagents.js +0 -997
- package/dist/content/templates.d.ts +0 -26
- package/dist/content/templates.js +0 -1692
- package/dist/content/track-render-context.d.ts +0 -18
- package/dist/content/track-render-context.js +0 -53
- package/dist/content/tree-command.d.ts +0 -1
- package/dist/content/tree-command.js +0 -64
- package/dist/content/utility-skills.d.ts +0 -30
- package/dist/content/utility-skills.js +0 -160
- package/dist/content/view-command.d.ts +0 -2
- package/dist/content/view-command.js +0 -92
- package/dist/delegation.d.ts +0 -649
- package/dist/delegation.js +0 -1539
- package/dist/early-loop.d.ts +0 -70
- package/dist/early-loop.js +0 -302
- package/dist/execution-topology.d.ts +0 -44
- package/dist/execution-topology.js +0 -95
- package/dist/gate-evidence.d.ts +0 -85
- package/dist/gate-evidence.js +0 -631
- package/dist/harness-adapters.d.ts +0 -151
- package/dist/harness-adapters.js +0 -756
- package/dist/harness-selection.d.ts +0 -31
- package/dist/harness-selection.js +0 -214
- package/dist/hook-schema.d.ts +0 -6
- package/dist/hook-schema.js +0 -114
- package/dist/hook-schemas/claude-hooks.v1.json +0 -10
- package/dist/hook-schemas/codex-hooks.v1.json +0 -10
- package/dist/hook-schemas/cursor-hooks.v1.json +0 -13
- package/dist/init-detect.d.ts +0 -2
- package/dist/init-detect.js +0 -50
- package/dist/internal/advance-stage/advance.d.ts +0 -89
- package/dist/internal/advance-stage/advance.js +0 -655
- package/dist/internal/advance-stage/cancel-run.d.ts +0 -8
- package/dist/internal/advance-stage/cancel-run.js +0 -19
- package/dist/internal/advance-stage/flow-state-coercion.d.ts +0 -3
- package/dist/internal/advance-stage/flow-state-coercion.js +0 -81
- package/dist/internal/advance-stage/helpers.d.ts +0 -14
- package/dist/internal/advance-stage/helpers.js +0 -145
- package/dist/internal/advance-stage/hook.d.ts +0 -8
- package/dist/internal/advance-stage/hook.js +0 -40
- package/dist/internal/advance-stage/parsers.d.ts +0 -72
- package/dist/internal/advance-stage/parsers.js +0 -357
- package/dist/internal/advance-stage/proactive-delegation-trace.d.ts +0 -24
- package/dist/internal/advance-stage/proactive-delegation-trace.js +0 -56
- package/dist/internal/advance-stage/review-loop.d.ts +0 -16
- package/dist/internal/advance-stage/review-loop.js +0 -199
- package/dist/internal/advance-stage/rewind.d.ts +0 -14
- package/dist/internal/advance-stage/rewind.js +0 -108
- package/dist/internal/advance-stage/start-flow.d.ts +0 -13
- package/dist/internal/advance-stage/start-flow.js +0 -241
- package/dist/internal/advance-stage/verify.d.ts +0 -21
- package/dist/internal/advance-stage/verify.js +0 -185
- package/dist/internal/advance-stage.d.ts +0 -7
- package/dist/internal/advance-stage.js +0 -138
- package/dist/internal/cohesion-contract-stub.d.ts +0 -24
- package/dist/internal/cohesion-contract-stub.js +0 -148
- package/dist/internal/compound-readiness.d.ts +0 -23
- package/dist/internal/compound-readiness.js +0 -102
- package/dist/internal/detect-public-api-changes.d.ts +0 -5
- package/dist/internal/detect-public-api-changes.js +0 -45
- package/dist/internal/detect-supply-chain-changes.d.ts +0 -6
- package/dist/internal/detect-supply-chain-changes.js +0 -138
- package/dist/internal/early-loop-status.d.ts +0 -7
- package/dist/internal/early-loop-status.js +0 -93
- package/dist/internal/envelope-validate.d.ts +0 -7
- package/dist/internal/envelope-validate.js +0 -66
- package/dist/internal/flow-state-repair.d.ts +0 -20
- package/dist/internal/flow-state-repair.js +0 -104
- package/dist/internal/plan-split-waves.d.ts +0 -190
- package/dist/internal/plan-split-waves.js +0 -764
- package/dist/internal/runtime-integrity.d.ts +0 -7
- package/dist/internal/runtime-integrity.js +0 -268
- package/dist/internal/slice-commit.d.ts +0 -7
- package/dist/internal/slice-commit.js +0 -619
- package/dist/internal/tdd-loop-status.d.ts +0 -14
- package/dist/internal/tdd-loop-status.js +0 -68
- package/dist/internal/tdd-red-evidence.d.ts +0 -7
- package/dist/internal/tdd-red-evidence.js +0 -153
- package/dist/internal/waiver-grant.d.ts +0 -62
- package/dist/internal/waiver-grant.js +0 -294
- package/dist/internal/wave-status.d.ts +0 -74
- package/dist/internal/wave-status.js +0 -506
- package/dist/managed-resources.d.ts +0 -53
- package/dist/managed-resources.js +0 -313
- package/dist/policy.d.ts +0 -10
- package/dist/policy.js +0 -167
- package/dist/retro-gate.d.ts +0 -9
- package/dist/retro-gate.js +0 -47
- package/dist/run-archive.d.ts +0 -61
- package/dist/run-archive.js +0 -391
- package/dist/runs.d.ts +0 -2
- package/dist/runs.js +0 -2
- package/dist/stack-detection.d.ts +0 -116
- package/dist/stack-detection.js +0 -489
- package/dist/streaming/event-stream.d.ts +0 -31
- package/dist/streaming/event-stream.js +0 -114
- package/dist/tdd-cycle.d.ts +0 -107
- package/dist/tdd-cycle.js +0 -289
- package/dist/tdd-verification-evidence.d.ts +0 -17
- package/dist/tdd-verification-evidence.js +0 -122
- package/dist/track-heuristics.d.ts +0 -27
- package/dist/track-heuristics.js +0 -154
- package/dist/util/slice-id.d.ts +0 -58
- package/dist/util/slice-id.js +0 -89
- package/dist/worktree-manager.d.ts +0 -20
- package/dist/worktree-manager.js +0 -108
|
@@ -1,619 +0,0 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { promisify } from "node:util";
|
|
4
|
-
import { readConfig, resolveLockfileTwinPolicy, resolveTddCommitMode, resolveTddIsolationMode, resolveTddWorktreeRoot } from "../config.js";
|
|
5
|
-
import { readDelegationLedger } from "../delegation.js";
|
|
6
|
-
import { exists } from "../fs-utils.js";
|
|
7
|
-
import { loadStackAdapter } from "../stack-detection.js";
|
|
8
|
-
import { cleanupWorktree, commitAndMergeBack, createSliceWorktree, WorktreeMergeConflictError, WorktreeUnsupportedError } from "../worktree-manager.js";
|
|
9
|
-
const execFileAsync = promisify(execFile);
|
|
10
|
-
function parseCsv(raw) {
|
|
11
|
-
return raw
|
|
12
|
-
.split(",")
|
|
13
|
-
.map((value) => value.trim())
|
|
14
|
-
.filter((value) => value.length > 0);
|
|
15
|
-
}
|
|
16
|
-
function normalizePathLike(value) {
|
|
17
|
-
const slashes = value.replace(/\\/gu, "/");
|
|
18
|
-
const withoutDot = slashes.replace(/^\.\//u, "");
|
|
19
|
-
return withoutDot.replace(/\/+$/u, "");
|
|
20
|
-
}
|
|
21
|
-
function parseSliceCommitArgs(tokens) {
|
|
22
|
-
let sliceId = "";
|
|
23
|
-
let spanId = "";
|
|
24
|
-
let taskId;
|
|
25
|
-
let title;
|
|
26
|
-
let runId;
|
|
27
|
-
let worktreePath;
|
|
28
|
-
const claimedPaths = [];
|
|
29
|
-
let prepareWorktree = false;
|
|
30
|
-
let json = false;
|
|
31
|
-
let quiet = false;
|
|
32
|
-
for (let i = 0; i < tokens.length; i += 1) {
|
|
33
|
-
const token = tokens[i];
|
|
34
|
-
const next = tokens[i + 1];
|
|
35
|
-
const valueFrom = (flag) => {
|
|
36
|
-
if (token.startsWith(`${flag}=`))
|
|
37
|
-
return token.slice(flag.length + 1);
|
|
38
|
-
if (token === flag && next && !next.startsWith("--")) {
|
|
39
|
-
i += 1;
|
|
40
|
-
return next;
|
|
41
|
-
}
|
|
42
|
-
throw new Error(`${flag} requires a value.`);
|
|
43
|
-
};
|
|
44
|
-
if (token === "--json") {
|
|
45
|
-
json = true;
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
if (token === "--quiet") {
|
|
49
|
-
quiet = true;
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
if (token === "--prepare-worktree") {
|
|
53
|
-
prepareWorktree = true;
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
if (token.startsWith("--slice=") || token === "--slice") {
|
|
57
|
-
sliceId = valueFrom("--slice").trim();
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
if (token.startsWith("--span-id=") || token === "--span-id") {
|
|
61
|
-
spanId = valueFrom("--span-id").trim();
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
if (token.startsWith("--task-id=") || token === "--task-id") {
|
|
65
|
-
taskId = valueFrom("--task-id").trim();
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
if (token.startsWith("--title=") || token === "--title") {
|
|
69
|
-
title = valueFrom("--title").trim();
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
if (token.startsWith("--run-id=") || token === "--run-id") {
|
|
73
|
-
runId = valueFrom("--run-id").trim();
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
if (token.startsWith("--worktree-path=") || token === "--worktree-path") {
|
|
77
|
-
const resolved = valueFrom("--worktree-path").trim();
|
|
78
|
-
if (resolved.length > 0) {
|
|
79
|
-
worktreePath = resolved;
|
|
80
|
-
}
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
if (token.startsWith("--claimed-paths=") || token === "--claimed-paths") {
|
|
84
|
-
claimedPaths.push(...parseCsv(valueFrom("--claimed-paths")));
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
if (token.startsWith("--claimed-path=") || token === "--claimed-path") {
|
|
88
|
-
const one = valueFrom("--claimed-path").trim();
|
|
89
|
-
if (one.length > 0)
|
|
90
|
-
claimedPaths.push(one);
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
throw new Error(`Unknown flag for internal slice-commit: ${token}`);
|
|
94
|
-
}
|
|
95
|
-
if (sliceId.length === 0) {
|
|
96
|
-
throw new Error("internal slice-commit requires --slice=<S-N>.");
|
|
97
|
-
}
|
|
98
|
-
if (spanId.length === 0) {
|
|
99
|
-
throw new Error("internal slice-commit requires --span-id=<span-id>.");
|
|
100
|
-
}
|
|
101
|
-
return {
|
|
102
|
-
sliceId,
|
|
103
|
-
spanId,
|
|
104
|
-
taskId,
|
|
105
|
-
title,
|
|
106
|
-
runId,
|
|
107
|
-
worktreePath,
|
|
108
|
-
claimedPaths,
|
|
109
|
-
prepareWorktree,
|
|
110
|
-
json,
|
|
111
|
-
quiet
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
function output(io, args, payload, channel = "stdout") {
|
|
115
|
-
if (args.quiet && channel === "stdout")
|
|
116
|
-
return;
|
|
117
|
-
const writer = channel === "stdout" ? io.stdout : io.stderr;
|
|
118
|
-
if (args.json) {
|
|
119
|
-
writer.write(`${JSON.stringify(payload)}\n`);
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
const message = typeof payload.message === "string"
|
|
123
|
-
? payload.message
|
|
124
|
-
: JSON.stringify(payload);
|
|
125
|
-
writer.write(`${message}\n`);
|
|
126
|
-
}
|
|
127
|
-
function parsePorcelainPaths(raw) {
|
|
128
|
-
const out = [];
|
|
129
|
-
for (const line of raw.split(/\r?\n/gu)) {
|
|
130
|
-
const trimmed = line.trimEnd();
|
|
131
|
-
if (trimmed.length < 4)
|
|
132
|
-
continue;
|
|
133
|
-
// porcelain line shape: XY<space><path>
|
|
134
|
-
const status = trimmed.slice(0, 2);
|
|
135
|
-
if (status === "??") {
|
|
136
|
-
const p = normalizePathLike(trimmed.slice(3).trim());
|
|
137
|
-
if (p.length > 0)
|
|
138
|
-
out.push(p);
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
let p = trimmed.slice(3).trim();
|
|
142
|
-
const renameIdx = p.indexOf(" -> ");
|
|
143
|
-
if (renameIdx >= 0) {
|
|
144
|
-
p = p.slice(renameIdx + 4);
|
|
145
|
-
}
|
|
146
|
-
p = normalizePathLike(p.replace(/^"/u, "").replace(/"$/u, ""));
|
|
147
|
-
if (p.length > 0)
|
|
148
|
-
out.push(p);
|
|
149
|
-
}
|
|
150
|
-
return [...new Set(out)];
|
|
151
|
-
}
|
|
152
|
-
async function gitChangedPaths(cwd) {
|
|
153
|
-
const { stdout: statusRaw } = await execFileAsync("git", ["status", "--porcelain", "-uall"], {
|
|
154
|
-
cwd
|
|
155
|
-
});
|
|
156
|
-
return parsePorcelainPaths(statusRaw);
|
|
157
|
-
}
|
|
158
|
-
function matchesClaimedPath(changedPath, claimedPaths) {
|
|
159
|
-
const changed = normalizePathLike(changedPath);
|
|
160
|
-
return claimedPaths.some((rawClaimed) => {
|
|
161
|
-
const claimed = normalizePathLike(rawClaimed);
|
|
162
|
-
if (claimed.length === 0)
|
|
163
|
-
return false;
|
|
164
|
-
if (changed === claimed)
|
|
165
|
-
return true;
|
|
166
|
-
return changed.startsWith(`${claimed}/`);
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
/**
|
|
170
|
-
* 7.6.0 — match a candidate path against a stack-adapter glob pattern.
|
|
171
|
-
*
|
|
172
|
-
* Adapter globs are intentionally simple: literal paths (`Cargo.toml`),
|
|
173
|
-
* recursive prefix (`**\/Cargo.toml`), or single-level wildcard
|
|
174
|
-
* (`*.csproj`). We translate those shapes here without pulling in a
|
|
175
|
-
* full glob library so the slice-commit hook stays dependency-light.
|
|
176
|
-
*/
|
|
177
|
-
function matchesAdapterGlob(candidate, glob) {
|
|
178
|
-
const normalizedCandidate = normalizePathLike(candidate);
|
|
179
|
-
const normalizedGlob = normalizePathLike(glob);
|
|
180
|
-
if (normalizedGlob.length === 0)
|
|
181
|
-
return false;
|
|
182
|
-
if (normalizedGlob.includes("**")) {
|
|
183
|
-
// `**/foo` → match either `foo` at root or any nested `foo`.
|
|
184
|
-
if (normalizedGlob.startsWith("**/")) {
|
|
185
|
-
const tail = normalizedGlob.slice(3);
|
|
186
|
-
if (tail === normalizedCandidate)
|
|
187
|
-
return true;
|
|
188
|
-
return normalizedCandidate.endsWith(`/${tail}`);
|
|
189
|
-
}
|
|
190
|
-
// Generic ** in the middle: collapse to suffix match for simplicity.
|
|
191
|
-
const tail = normalizedGlob.split("**/").pop() ?? "";
|
|
192
|
-
return tail.length > 0 && normalizedCandidate.endsWith(tail);
|
|
193
|
-
}
|
|
194
|
-
if (normalizedGlob.includes("*")) {
|
|
195
|
-
// Single-segment wildcard like `*.csproj`. Convert to a basic regex.
|
|
196
|
-
const regexSrc = normalizedGlob
|
|
197
|
-
.split("/")
|
|
198
|
-
.map((segment) => segment
|
|
199
|
-
.replace(/[.+?^${}()|[\]\\]/gu, "\\$&")
|
|
200
|
-
.replace(/\*/gu, "[^/]*"))
|
|
201
|
-
.join("/");
|
|
202
|
-
return new RegExp(`^${regexSrc}$`, "u").test(normalizedCandidate);
|
|
203
|
-
}
|
|
204
|
-
return normalizedGlob === normalizedCandidate;
|
|
205
|
-
}
|
|
206
|
-
/**
|
|
207
|
-
* Find lockfile twins whose manifestGlob matches at least one claimed
|
|
208
|
-
* path. The returned twins are the candidates whose lockfileGlob we
|
|
209
|
-
* should auto-include / auto-revert when they drift.
|
|
210
|
-
*/
|
|
211
|
-
function activeLockfileTwins(adapter, claimedPaths) {
|
|
212
|
-
if (adapter.lockfileTwins.length === 0)
|
|
213
|
-
return [];
|
|
214
|
-
const active = [];
|
|
215
|
-
for (const twin of adapter.lockfileTwins) {
|
|
216
|
-
const claimedManifest = claimedPaths.some((claimed) => matchesAdapterGlob(claimed, twin.manifestGlob));
|
|
217
|
-
if (claimedManifest)
|
|
218
|
-
active.push(twin);
|
|
219
|
-
}
|
|
220
|
-
return active;
|
|
221
|
-
}
|
|
222
|
-
/**
|
|
223
|
-
* Partition a candidate path: `is it a lockfile twin we should
|
|
224
|
-
* auto-handle?`. Returns the twin entry that matches, or null.
|
|
225
|
-
*/
|
|
226
|
-
function findMatchingLockfileTwin(changedPath, twins) {
|
|
227
|
-
for (const twin of twins) {
|
|
228
|
-
if (matchesAdapterGlob(changedPath, twin.lockfileGlob)) {
|
|
229
|
-
return twin;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
return null;
|
|
233
|
-
}
|
|
234
|
-
async function resolveClaimedPathsFromLedger(projectRoot, args) {
|
|
235
|
-
const ledger = await readDelegationLedger(projectRoot);
|
|
236
|
-
const matches = ledger.entries.filter((entry) => entry.stage === "tdd" &&
|
|
237
|
-
entry.agent === "slice-builder" &&
|
|
238
|
-
entry.sliceId === args.sliceId &&
|
|
239
|
-
entry.spanId === args.spanId &&
|
|
240
|
-
(!args.runId || entry.runId === args.runId) &&
|
|
241
|
-
Array.isArray(entry.claimedPaths) &&
|
|
242
|
-
entry.claimedPaths.length > 0);
|
|
243
|
-
matches.sort((a, b) => {
|
|
244
|
-
const aTs = a.ts ?? a.startTs ?? "";
|
|
245
|
-
const bTs = b.ts ?? b.startTs ?? "";
|
|
246
|
-
return aTs < bTs ? 1 : aTs > bTs ? -1 : 0;
|
|
247
|
-
});
|
|
248
|
-
const fromLedger = matches[0]?.claimedPaths ?? [];
|
|
249
|
-
return [...new Set(fromLedger.map((p) => normalizePathLike(p)).filter((p) => p.length > 0))];
|
|
250
|
-
}
|
|
251
|
-
export async function runSliceCommitCommand(projectRoot, tokens, io) {
|
|
252
|
-
let args;
|
|
253
|
-
try {
|
|
254
|
-
args = parseSliceCommitArgs(tokens);
|
|
255
|
-
}
|
|
256
|
-
catch (err) {
|
|
257
|
-
io.stderr.write(`cclaw internal slice-commit: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
258
|
-
return 1;
|
|
259
|
-
}
|
|
260
|
-
const config = await readConfig(projectRoot).catch(() => null);
|
|
261
|
-
const commitMode = resolveTddCommitMode(config);
|
|
262
|
-
const isolationMode = resolveTddIsolationMode(config);
|
|
263
|
-
const worktreeRoot = resolveTddWorktreeRoot(config);
|
|
264
|
-
const lockfileTwinPolicy = resolveLockfileTwinPolicy(config);
|
|
265
|
-
const stackAdapter = await loadStackAdapter(projectRoot);
|
|
266
|
-
const gitPresent = await exists(path.join(projectRoot, ".git"));
|
|
267
|
-
if (args.prepareWorktree) {
|
|
268
|
-
if (!gitPresent) {
|
|
269
|
-
output(io, args, {
|
|
270
|
-
ok: true,
|
|
271
|
-
skipped: true,
|
|
272
|
-
reason: "no-git",
|
|
273
|
-
message: "slice-worktree skipped: .git is missing"
|
|
274
|
-
});
|
|
275
|
-
return 0;
|
|
276
|
-
}
|
|
277
|
-
if (isolationMode === "in-place") {
|
|
278
|
-
output(io, args, {
|
|
279
|
-
ok: true,
|
|
280
|
-
skipped: true,
|
|
281
|
-
reason: "isolation-in-place",
|
|
282
|
-
isolationMode,
|
|
283
|
-
message: "slice-worktree skipped: tdd.isolationMode=in-place"
|
|
284
|
-
});
|
|
285
|
-
return 0;
|
|
286
|
-
}
|
|
287
|
-
try {
|
|
288
|
-
const { stdout } = await execFileAsync("git", ["rev-parse", "HEAD"], { cwd: projectRoot });
|
|
289
|
-
const prepared = await createSliceWorktree(args.sliceId, stdout.trim(), args.claimedPaths, {
|
|
290
|
-
projectRoot,
|
|
291
|
-
worktreeRoot
|
|
292
|
-
});
|
|
293
|
-
output(io, args, {
|
|
294
|
-
ok: true,
|
|
295
|
-
prepared: true,
|
|
296
|
-
sliceId: args.sliceId,
|
|
297
|
-
spanId: args.spanId,
|
|
298
|
-
worktreePath: prepared.path,
|
|
299
|
-
baseRef: prepared.ref
|
|
300
|
-
});
|
|
301
|
-
return 0;
|
|
302
|
-
}
|
|
303
|
-
catch (error) {
|
|
304
|
-
if (error instanceof WorktreeUnsupportedError) {
|
|
305
|
-
output(io, args, {
|
|
306
|
-
ok: true,
|
|
307
|
-
skipped: true,
|
|
308
|
-
reason: "worktree-unavailable",
|
|
309
|
-
degradedCommitMode: "agent-required",
|
|
310
|
-
message: error.message
|
|
311
|
-
});
|
|
312
|
-
return 0;
|
|
313
|
-
}
|
|
314
|
-
output(io, args, {
|
|
315
|
-
ok: false,
|
|
316
|
-
errorCode: "worktree_prepare_failed",
|
|
317
|
-
details: {
|
|
318
|
-
message: error instanceof Error ? error.message : String(error)
|
|
319
|
-
},
|
|
320
|
-
message: `worktree_prepare_failed: ${error instanceof Error ? error.message : String(error)}`
|
|
321
|
-
}, "stderr");
|
|
322
|
-
return 1;
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
if (commitMode !== "managed-per-slice") {
|
|
326
|
-
output(io, args, {
|
|
327
|
-
ok: true,
|
|
328
|
-
skipped: true,
|
|
329
|
-
reason: "commit-mode-not-managed",
|
|
330
|
-
commitMode,
|
|
331
|
-
message: `slice-commit skipped: commitMode=${commitMode}`
|
|
332
|
-
});
|
|
333
|
-
return 0;
|
|
334
|
-
}
|
|
335
|
-
if (!gitPresent) {
|
|
336
|
-
output(io, args, {
|
|
337
|
-
ok: true,
|
|
338
|
-
skipped: true,
|
|
339
|
-
reason: "no-git",
|
|
340
|
-
message: "slice-commit skipped: .git is missing"
|
|
341
|
-
});
|
|
342
|
-
return 0;
|
|
343
|
-
}
|
|
344
|
-
const claimedPaths = args.claimedPaths.length > 0
|
|
345
|
-
? [...new Set(args.claimedPaths.map((p) => normalizePathLike(p)).filter((p) => p.length > 0))]
|
|
346
|
-
: await resolveClaimedPathsFromLedger(projectRoot, args);
|
|
347
|
-
if (claimedPaths.length === 0) {
|
|
348
|
-
output(io, args, {
|
|
349
|
-
ok: false,
|
|
350
|
-
errorCode: "slice_commit_claimed_paths_missing",
|
|
351
|
-
details: {
|
|
352
|
-
sliceId: args.sliceId,
|
|
353
|
-
spanId: args.spanId
|
|
354
|
-
},
|
|
355
|
-
message: `slice_commit_claimed_paths_missing: no claimed paths for ${args.sliceId}/${args.spanId}`
|
|
356
|
-
}, "stderr");
|
|
357
|
-
return 2;
|
|
358
|
-
}
|
|
359
|
-
let managedWorktreePath = null;
|
|
360
|
-
let activeCwd = projectRoot;
|
|
361
|
-
let degradedToInPlace = false;
|
|
362
|
-
const requestedWorktreePath = typeof args.worktreePath === "string" && args.worktreePath.trim().length > 0
|
|
363
|
-
? path.resolve(projectRoot, args.worktreePath.trim())
|
|
364
|
-
: null;
|
|
365
|
-
if (requestedWorktreePath && await exists(requestedWorktreePath)) {
|
|
366
|
-
managedWorktreePath = requestedWorktreePath;
|
|
367
|
-
activeCwd = requestedWorktreePath;
|
|
368
|
-
}
|
|
369
|
-
else if (isolationMode !== "in-place") {
|
|
370
|
-
try {
|
|
371
|
-
const { stdout } = await execFileAsync("git", ["rev-parse", "HEAD"], { cwd: projectRoot });
|
|
372
|
-
const prepared = await createSliceWorktree(args.sliceId, stdout.trim(), claimedPaths, {
|
|
373
|
-
projectRoot,
|
|
374
|
-
worktreeRoot
|
|
375
|
-
});
|
|
376
|
-
managedWorktreePath = prepared.path;
|
|
377
|
-
activeCwd = prepared.path;
|
|
378
|
-
}
|
|
379
|
-
catch (error) {
|
|
380
|
-
if (error instanceof WorktreeUnsupportedError) {
|
|
381
|
-
output(io, args, {
|
|
382
|
-
ok: true,
|
|
383
|
-
skipped: true,
|
|
384
|
-
reason: "worktree-unavailable",
|
|
385
|
-
degradedCommitMode: "agent-required",
|
|
386
|
-
message: error.message
|
|
387
|
-
});
|
|
388
|
-
return 0;
|
|
389
|
-
}
|
|
390
|
-
output(io, args, {
|
|
391
|
-
ok: false,
|
|
392
|
-
errorCode: "worktree_prepare_failed",
|
|
393
|
-
details: {
|
|
394
|
-
message: error instanceof Error ? error.message : String(error)
|
|
395
|
-
},
|
|
396
|
-
message: `worktree_prepare_failed: ${error instanceof Error ? error.message : String(error)}`
|
|
397
|
-
}, "stderr");
|
|
398
|
-
return 1;
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
const cleanupManagedWorktree = async () => {
|
|
402
|
-
if (!managedWorktreePath)
|
|
403
|
-
return;
|
|
404
|
-
await cleanupWorktree(managedWorktreePath, { projectRoot }).catch(() => undefined);
|
|
405
|
-
};
|
|
406
|
-
let changedPaths = await gitChangedPaths(activeCwd);
|
|
407
|
-
if (changedPaths.length === 0 && managedWorktreePath && activeCwd !== projectRoot) {
|
|
408
|
-
const rootChangedPaths = await gitChangedPaths(projectRoot);
|
|
409
|
-
if (rootChangedPaths.length > 0) {
|
|
410
|
-
activeCwd = projectRoot;
|
|
411
|
-
changedPaths = rootChangedPaths;
|
|
412
|
-
degradedToInPlace = true;
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
if (changedPaths.length === 0) {
|
|
416
|
-
await cleanupManagedWorktree();
|
|
417
|
-
output(io, args, {
|
|
418
|
-
ok: true,
|
|
419
|
-
skipped: true,
|
|
420
|
-
reason: "no-changes",
|
|
421
|
-
message: `slice-commit skipped: no working-tree changes for ${args.sliceId}`
|
|
422
|
-
});
|
|
423
|
-
return 0;
|
|
424
|
-
}
|
|
425
|
-
const initialDrift = changedPaths.filter((p) => !matchesClaimedPath(p, claimedPaths));
|
|
426
|
-
const twinsForCommit = activeLockfileTwins(stackAdapter, claimedPaths);
|
|
427
|
-
// 7.6.0 — split drift into "lockfile twin drift" (handle per policy)
|
|
428
|
-
// vs "true drift" (always rejected).
|
|
429
|
-
const lockfileTwinDrift = [];
|
|
430
|
-
const trueDrift = [];
|
|
431
|
-
for (const driftPath of initialDrift) {
|
|
432
|
-
const twin = findMatchingLockfileTwin(driftPath, twinsForCommit);
|
|
433
|
-
if (twin) {
|
|
434
|
-
lockfileTwinDrift.push({ path: driftPath, twin });
|
|
435
|
-
}
|
|
436
|
-
else {
|
|
437
|
-
trueDrift.push(driftPath);
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
// Report a separate true-drift error when there is actual non-twin
|
|
441
|
-
// drift, regardless of policy: the operator's claim should still
|
|
442
|
-
// cover everything they changed.
|
|
443
|
-
if (trueDrift.length > 0) {
|
|
444
|
-
output(io, args, {
|
|
445
|
-
ok: false,
|
|
446
|
-
errorCode: "slice_commit_path_drift",
|
|
447
|
-
details: {
|
|
448
|
-
sliceId: args.sliceId,
|
|
449
|
-
spanId: args.spanId,
|
|
450
|
-
claimedPaths,
|
|
451
|
-
driftPaths: trueDrift
|
|
452
|
-
},
|
|
453
|
-
message: `slice_commit_path_drift: ${trueDrift.join(", ")}`
|
|
454
|
-
}, "stderr");
|
|
455
|
-
return 2;
|
|
456
|
-
}
|
|
457
|
-
// strict-fence: lockfile twins still count as drift.
|
|
458
|
-
if (lockfileTwinDrift.length > 0 && lockfileTwinPolicy === "strict-fence") {
|
|
459
|
-
const driftPaths = lockfileTwinDrift.map((entry) => entry.path);
|
|
460
|
-
output(io, args, {
|
|
461
|
-
ok: false,
|
|
462
|
-
errorCode: "slice_commit_path_drift",
|
|
463
|
-
details: {
|
|
464
|
-
sliceId: args.sliceId,
|
|
465
|
-
spanId: args.spanId,
|
|
466
|
-
claimedPaths,
|
|
467
|
-
driftPaths,
|
|
468
|
-
lockfileTwinPolicy,
|
|
469
|
-
stackAdapterId: stackAdapter.id
|
|
470
|
-
},
|
|
471
|
-
message: `slice_commit_path_drift: ${driftPaths.join(", ")} (lockfileTwinPolicy=strict-fence)`
|
|
472
|
-
}, "stderr");
|
|
473
|
-
return 2;
|
|
474
|
-
}
|
|
475
|
-
// auto-revert: restore the lockfile, then exclude from changed set.
|
|
476
|
-
const revertedTwinPaths = [];
|
|
477
|
-
if (lockfileTwinDrift.length > 0 && lockfileTwinPolicy === "auto-revert") {
|
|
478
|
-
for (const entry of lockfileTwinDrift) {
|
|
479
|
-
try {
|
|
480
|
-
await execFileAsync("git", ["restore", "--", entry.path], { cwd: activeCwd });
|
|
481
|
-
revertedTwinPaths.push(entry.path);
|
|
482
|
-
}
|
|
483
|
-
catch {
|
|
484
|
-
// Fall through; if restore fails the drift will reappear in the
|
|
485
|
-
// recomputed status and we'll reject as drift.
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
changedPaths = await gitChangedPaths(activeCwd);
|
|
489
|
-
const remainingDrift = changedPaths.filter((p) => !matchesClaimedPath(p, claimedPaths));
|
|
490
|
-
if (remainingDrift.length > 0) {
|
|
491
|
-
output(io, args, {
|
|
492
|
-
ok: false,
|
|
493
|
-
errorCode: "slice_commit_path_drift",
|
|
494
|
-
details: {
|
|
495
|
-
sliceId: args.sliceId,
|
|
496
|
-
spanId: args.spanId,
|
|
497
|
-
claimedPaths,
|
|
498
|
-
driftPaths: remainingDrift,
|
|
499
|
-
lockfileTwinPolicy,
|
|
500
|
-
stackAdapterId: stackAdapter.id
|
|
501
|
-
},
|
|
502
|
-
message: `slice_commit_path_drift: ${remainingDrift.join(", ")}`
|
|
503
|
-
}, "stderr");
|
|
504
|
-
return 2;
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
// auto-include: add the twin path(s) to the effective claim so the
|
|
508
|
-
// commit picks them up. We don't mutate the persisted claim — only
|
|
509
|
-
// the in-memory list used for the upcoming `git add`.
|
|
510
|
-
const effectiveCommitPaths = [...claimedPaths];
|
|
511
|
-
const includedTwinPaths = [];
|
|
512
|
-
if (lockfileTwinDrift.length > 0 && lockfileTwinPolicy === "auto-include") {
|
|
513
|
-
for (const entry of lockfileTwinDrift) {
|
|
514
|
-
if (!effectiveCommitPaths.includes(entry.path)) {
|
|
515
|
-
effectiveCommitPaths.push(entry.path);
|
|
516
|
-
}
|
|
517
|
-
includedTwinPaths.push(entry.path);
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
const changedInClaim = changedPaths.filter((p) => matchesClaimedPath(p, claimedPaths) ||
|
|
521
|
-
(lockfileTwinPolicy === "auto-include" &&
|
|
522
|
-
findMatchingLockfileTwin(p, twinsForCommit) !== null));
|
|
523
|
-
if (changedInClaim.length === 0) {
|
|
524
|
-
await cleanupManagedWorktree();
|
|
525
|
-
output(io, args, {
|
|
526
|
-
ok: true,
|
|
527
|
-
skipped: true,
|
|
528
|
-
reason: "claimed-paths-unchanged",
|
|
529
|
-
message: `slice-commit skipped: no changes within claimed paths for ${args.sliceId}`
|
|
530
|
-
});
|
|
531
|
-
return 0;
|
|
532
|
-
}
|
|
533
|
-
try {
|
|
534
|
-
await execFileAsync("git", ["add", "--", ...effectiveCommitPaths], {
|
|
535
|
-
cwd: activeCwd
|
|
536
|
-
});
|
|
537
|
-
const taskPart = args.taskId && args.taskId.length > 0 ? args.taskId : "task";
|
|
538
|
-
const titlePart = args.title && args.title.length > 0 ? args.title : "slice update";
|
|
539
|
-
const header = `${args.sliceId}/${taskPart}: ${titlePart}`;
|
|
540
|
-
const body = [
|
|
541
|
-
`span-id: ${args.spanId}`,
|
|
542
|
-
`run-id: ${args.runId ?? "unknown"}`,
|
|
543
|
-
"phase-cycle: red->green->refactor->doc"
|
|
544
|
-
].join("\n");
|
|
545
|
-
await execFileAsync("git", ["commit", "-m", header, "-m", body], {
|
|
546
|
-
cwd: activeCwd
|
|
547
|
-
});
|
|
548
|
-
}
|
|
549
|
-
catch (err) {
|
|
550
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
551
|
-
if (/nothing to commit/iu.test(message)) {
|
|
552
|
-
await cleanupManagedWorktree();
|
|
553
|
-
output(io, args, {
|
|
554
|
-
ok: true,
|
|
555
|
-
skipped: true,
|
|
556
|
-
reason: "nothing-to-commit",
|
|
557
|
-
message: `slice-commit skipped: nothing to commit for ${args.sliceId}`
|
|
558
|
-
});
|
|
559
|
-
return 0;
|
|
560
|
-
}
|
|
561
|
-
output(io, args, {
|
|
562
|
-
ok: false,
|
|
563
|
-
errorCode: "slice_commit_failed",
|
|
564
|
-
details: { message },
|
|
565
|
-
message: `slice_commit_failed: ${message}`
|
|
566
|
-
}, "stderr");
|
|
567
|
-
return 1;
|
|
568
|
-
}
|
|
569
|
-
const { stdout: shaStdout } = await execFileAsync("git", ["rev-parse", "HEAD"], {
|
|
570
|
-
cwd: activeCwd
|
|
571
|
-
});
|
|
572
|
-
let commitSha = shaStdout.trim();
|
|
573
|
-
if (managedWorktreePath && activeCwd !== projectRoot) {
|
|
574
|
-
try {
|
|
575
|
-
const merged = await commitAndMergeBack(activeCwd, `merge ${args.sliceId}`, { projectRoot });
|
|
576
|
-
commitSha = merged.commitSha;
|
|
577
|
-
}
|
|
578
|
-
catch (error) {
|
|
579
|
-
if (error instanceof WorktreeMergeConflictError) {
|
|
580
|
-
output(io, args, {
|
|
581
|
-
ok: false,
|
|
582
|
-
errorCode: "worktree_merge_conflict",
|
|
583
|
-
details: {
|
|
584
|
-
sliceId: args.sliceId,
|
|
585
|
-
spanId: args.spanId,
|
|
586
|
-
worktreePath: activeCwd,
|
|
587
|
-
message: error.message
|
|
588
|
-
},
|
|
589
|
-
message: error.message
|
|
590
|
-
}, "stderr");
|
|
591
|
-
return 2;
|
|
592
|
-
}
|
|
593
|
-
output(io, args, {
|
|
594
|
-
ok: false,
|
|
595
|
-
errorCode: "slice_commit_failed",
|
|
596
|
-
details: { message: error instanceof Error ? error.message : String(error) },
|
|
597
|
-
message: `slice_commit_failed: ${error instanceof Error ? error.message : String(error)}`
|
|
598
|
-
}, "stderr");
|
|
599
|
-
return 1;
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
await cleanupManagedWorktree();
|
|
603
|
-
output(io, args, {
|
|
604
|
-
ok: true,
|
|
605
|
-
commitSha,
|
|
606
|
-
sliceId: args.sliceId,
|
|
607
|
-
spanId: args.spanId,
|
|
608
|
-
claimedPaths,
|
|
609
|
-
changedPaths: changedInClaim,
|
|
610
|
-
worktreePath: managedWorktreePath ?? undefined,
|
|
611
|
-
degradedToInPlace: degradedToInPlace || undefined,
|
|
612
|
-
lockfileTwinPolicy,
|
|
613
|
-
lockfileTwinsIncluded: includedTwinPaths.length > 0 ? includedTwinPaths : undefined,
|
|
614
|
-
lockfileTwinsReverted: revertedTwinPaths.length > 0 ? revertedTwinPaths : undefined,
|
|
615
|
-
stackAdapterId: stackAdapter.id,
|
|
616
|
-
message: `slice commit created for ${args.sliceId}: ${commitSha}`
|
|
617
|
-
});
|
|
618
|
-
return 0;
|
|
619
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { Writable } from "node:stream";
|
|
2
|
-
import { type RalphLoopStatus } from "../tdd-cycle.js";
|
|
3
|
-
interface InternalIo {
|
|
4
|
-
stdout: Writable;
|
|
5
|
-
stderr: Writable;
|
|
6
|
-
}
|
|
7
|
-
/**
|
|
8
|
-
* Produces a one-line "Ralph Loop: iter=X, slices=Y, acClosed=Z, redOpen=..."
|
|
9
|
-
* summary — suitable for bootstrap surfaces where the user
|
|
10
|
-
* just needs a progress indicator, not the full slice breakdown.
|
|
11
|
-
*/
|
|
12
|
-
export declare function formatRalphLoopStatusLine(status: RalphLoopStatus): string;
|
|
13
|
-
export declare function runTddLoopStatusCommand(projectRoot: string, argv: string[], io: InternalIo): Promise<number>;
|
|
14
|
-
export {};
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { RUNTIME_ROOT } from "../constants.js";
|
|
4
|
-
import { writeFileSafe } from "../fs-utils.js";
|
|
5
|
-
import { readFlowState } from "../runs.js";
|
|
6
|
-
import { computeRalphLoopStatus, parseTddCycleLog } from "../tdd-cycle.js";
|
|
7
|
-
function parseArgs(tokens) {
|
|
8
|
-
const args = { json: false, quiet: false, write: true };
|
|
9
|
-
for (const token of tokens) {
|
|
10
|
-
if (token === "--json")
|
|
11
|
-
args.json = true;
|
|
12
|
-
else if (token === "--quiet")
|
|
13
|
-
args.quiet = true;
|
|
14
|
-
else if (token === "--no-write")
|
|
15
|
-
args.write = false;
|
|
16
|
-
else if (token === "--write")
|
|
17
|
-
args.write = true;
|
|
18
|
-
else
|
|
19
|
-
throw new Error(`Unknown tdd-loop-status flag: ${token}`);
|
|
20
|
-
}
|
|
21
|
-
return args;
|
|
22
|
-
}
|
|
23
|
-
function stateDir(projectRoot) {
|
|
24
|
-
return path.join(projectRoot, RUNTIME_ROOT, "state");
|
|
25
|
-
}
|
|
26
|
-
async function readCycleLog(projectRoot) {
|
|
27
|
-
const filePath = path.join(stateDir(projectRoot), "tdd-cycle-log.jsonl");
|
|
28
|
-
try {
|
|
29
|
-
return await fs.readFile(filePath, "utf8");
|
|
30
|
-
}
|
|
31
|
-
catch (err) {
|
|
32
|
-
if (err.code === "ENOENT")
|
|
33
|
-
return "";
|
|
34
|
-
throw err;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Produces a one-line "Ralph Loop: iter=X, slices=Y, acClosed=Z, redOpen=..."
|
|
39
|
-
* summary — suitable for bootstrap surfaces where the user
|
|
40
|
-
* just needs a progress indicator, not the full slice breakdown.
|
|
41
|
-
*/
|
|
42
|
-
export function formatRalphLoopStatusLine(status) {
|
|
43
|
-
const redOpen = status.redOpenSlices.length > 0
|
|
44
|
-
? status.redOpenSlices.join(",")
|
|
45
|
-
: "none";
|
|
46
|
-
return `Ralph Loop: iter=${status.loopIteration}, slices=${status.sliceCount}, acClosed=${status.acClosed.length}, redOpen=${redOpen}`;
|
|
47
|
-
}
|
|
48
|
-
export async function runTddLoopStatusCommand(projectRoot, argv, io) {
|
|
49
|
-
const args = parseArgs(argv);
|
|
50
|
-
const flow = await readFlowState(projectRoot).catch(() => null);
|
|
51
|
-
const runId = flow?.activeRunId ?? "active";
|
|
52
|
-
const text = await readCycleLog(projectRoot);
|
|
53
|
-
const entries = parseTddCycleLog(text);
|
|
54
|
-
const status = computeRalphLoopStatus(entries, { runId });
|
|
55
|
-
if (args.write) {
|
|
56
|
-
const target = path.join(stateDir(projectRoot), "ralph-loop.json");
|
|
57
|
-
await writeFileSafe(target, `${JSON.stringify(status, null, 2)}\n`);
|
|
58
|
-
}
|
|
59
|
-
if (!args.quiet) {
|
|
60
|
-
if (args.json) {
|
|
61
|
-
io.stdout.write(`${JSON.stringify(status, null, 2)}\n`);
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
io.stdout.write(`${formatRalphLoopStatusLine(status)}\n`);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return 0;
|
|
68
|
-
}
|