cclaw-cli 0.51.29 → 0.55.2

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.
Files changed (151) hide show
  1. package/README.md +22 -16
  2. package/dist/artifact-linter/brainstorm.d.ts +2 -0
  3. package/dist/artifact-linter/brainstorm.js +245 -0
  4. package/dist/artifact-linter/design.d.ts +2 -0
  5. package/dist/artifact-linter/design.js +323 -0
  6. package/dist/artifact-linter/plan.d.ts +2 -0
  7. package/dist/artifact-linter/plan.js +162 -0
  8. package/dist/artifact-linter/review-army.d.ts +24 -0
  9. package/dist/artifact-linter/review-army.js +365 -0
  10. package/dist/artifact-linter/review.d.ts +2 -0
  11. package/dist/artifact-linter/review.js +65 -0
  12. package/dist/artifact-linter/scope.d.ts +2 -0
  13. package/dist/artifact-linter/scope.js +115 -0
  14. package/dist/artifact-linter/shared.d.ts +246 -0
  15. package/dist/artifact-linter/shared.js +1488 -0
  16. package/dist/artifact-linter/ship.d.ts +2 -0
  17. package/dist/artifact-linter/ship.js +46 -0
  18. package/dist/artifact-linter/spec.d.ts +2 -0
  19. package/dist/artifact-linter/spec.js +108 -0
  20. package/dist/artifact-linter/tdd.d.ts +2 -0
  21. package/dist/artifact-linter/tdd.js +124 -0
  22. package/dist/artifact-linter.d.ts +4 -76
  23. package/dist/artifact-linter.js +56 -2949
  24. package/dist/cli.d.ts +2 -18
  25. package/dist/cli.js +8 -246
  26. package/dist/codex-feature-flag.d.ts +1 -1
  27. package/dist/codex-feature-flag.js +1 -1
  28. package/dist/config.d.ts +3 -2
  29. package/dist/config.js +67 -3
  30. package/dist/constants.d.ts +1 -7
  31. package/dist/constants.js +9 -15
  32. package/dist/content/cancel-command.js +2 -2
  33. package/dist/content/closeout-guidance.js +13 -10
  34. package/dist/content/core-agents.d.ts +18 -0
  35. package/dist/content/core-agents.js +51 -7
  36. package/dist/content/decision-protocol.d.ts +1 -1
  37. package/dist/content/decision-protocol.js +1 -1
  38. package/dist/content/examples.js +6 -6
  39. package/dist/content/harness-doc.js +20 -2
  40. package/dist/content/hook-inline-snippets.d.ts +17 -4
  41. package/dist/content/hook-inline-snippets.js +218 -5
  42. package/dist/content/hook-manifest.d.ts +2 -2
  43. package/dist/content/hook-manifest.js +2 -2
  44. package/dist/content/hooks.d.ts +1 -0
  45. package/dist/content/hooks.js +32 -137
  46. package/dist/content/idea-command.d.ts +8 -0
  47. package/dist/content/{ideate-command.js → idea-command.js} +57 -50
  48. package/dist/content/idea-frames.d.ts +31 -0
  49. package/dist/content/{ideate-frames.js → idea-frames.js} +9 -9
  50. package/dist/content/idea-ranking.d.ts +25 -0
  51. package/dist/content/{ideate-ranking.js → idea-ranking.js} +5 -5
  52. package/dist/content/iron-laws.d.ts +0 -1
  53. package/dist/content/iron-laws.js +31 -16
  54. package/dist/content/learnings.js +1 -1
  55. package/dist/content/meta-skill.js +11 -13
  56. package/dist/content/node-hooks.d.ts +10 -0
  57. package/dist/content/node-hooks.js +45 -11
  58. package/dist/content/opencode-plugin.js +3 -3
  59. package/dist/content/session-hooks.js +1 -1
  60. package/dist/content/skills.js +19 -7
  61. package/dist/content/stage-command.js +1 -1
  62. package/dist/content/stage-schema.js +44 -2
  63. package/dist/content/stages/_lint-metadata/index.js +26 -2
  64. package/dist/content/stages/brainstorm.js +13 -7
  65. package/dist/content/stages/design.js +16 -11
  66. package/dist/content/stages/plan.js +9 -6
  67. package/dist/content/stages/review.js +4 -4
  68. package/dist/content/stages/schema-types.d.ts +1 -1
  69. package/dist/content/stages/scope.js +15 -12
  70. package/dist/content/stages/ship.js +2 -2
  71. package/dist/content/stages/spec.js +9 -3
  72. package/dist/content/stages/tdd.js +14 -4
  73. package/dist/content/start-command.d.ts +2 -2
  74. package/dist/content/start-command.js +24 -21
  75. package/dist/content/status-command.js +8 -8
  76. package/dist/content/subagents.js +61 -7
  77. package/dist/content/templates.d.ts +1 -1
  78. package/dist/content/templates.js +104 -152
  79. package/dist/content/tree-command.js +2 -2
  80. package/dist/content/utility-skills.d.ts +2 -2
  81. package/dist/content/utility-skills.js +2 -2
  82. package/dist/content/view-command.js +4 -2
  83. package/dist/delegation.d.ts +2 -0
  84. package/dist/delegation.js +2 -1
  85. package/dist/early-loop.d.ts +66 -0
  86. package/dist/early-loop.js +275 -0
  87. package/dist/flow-state.d.ts +1 -1
  88. package/dist/flow-state.js +1 -1
  89. package/dist/gate-evidence.d.ts +8 -0
  90. package/dist/gate-evidence.js +141 -5
  91. package/dist/harness-adapters.d.ts +2 -2
  92. package/dist/harness-adapters.js +54 -122
  93. package/dist/harness-selection.d.ts +31 -0
  94. package/dist/harness-selection.js +214 -0
  95. package/dist/install.js +166 -38
  96. package/dist/internal/advance-stage/advance.d.ts +50 -0
  97. package/dist/internal/advance-stage/advance.js +480 -0
  98. package/dist/internal/advance-stage/cancel-run.d.ts +8 -0
  99. package/dist/internal/advance-stage/cancel-run.js +19 -0
  100. package/dist/internal/advance-stage/flow-state-coercion.d.ts +3 -0
  101. package/dist/internal/advance-stage/flow-state-coercion.js +81 -0
  102. package/dist/internal/advance-stage/helpers.d.ts +14 -0
  103. package/dist/internal/advance-stage/helpers.js +145 -0
  104. package/dist/internal/advance-stage/hook.d.ts +8 -0
  105. package/dist/internal/advance-stage/hook.js +40 -0
  106. package/dist/internal/advance-stage/parsers.d.ts +54 -0
  107. package/dist/internal/advance-stage/parsers.js +307 -0
  108. package/dist/internal/advance-stage/review-loop.d.ts +7 -0
  109. package/dist/internal/advance-stage/review-loop.js +170 -0
  110. package/dist/internal/advance-stage/rewind.d.ts +14 -0
  111. package/dist/internal/advance-stage/rewind.js +108 -0
  112. package/dist/internal/advance-stage/start-flow.d.ts +11 -0
  113. package/dist/internal/advance-stage/start-flow.js +136 -0
  114. package/dist/internal/advance-stage/verify.d.ts +29 -0
  115. package/dist/internal/advance-stage/verify.js +225 -0
  116. package/dist/internal/advance-stage.js +21 -1470
  117. package/dist/internal/compound-readiness.d.ts +1 -1
  118. package/dist/internal/compound-readiness.js +2 -2
  119. package/dist/internal/early-loop-status.d.ts +7 -0
  120. package/dist/internal/early-loop-status.js +90 -0
  121. package/dist/internal/runtime-integrity.d.ts +7 -0
  122. package/dist/internal/runtime-integrity.js +288 -0
  123. package/dist/internal/tdd-red-evidence.js +1 -1
  124. package/dist/knowledge-store.d.ts +3 -8
  125. package/dist/knowledge-store.js +16 -29
  126. package/dist/managed-resources.js +24 -2
  127. package/dist/policy.js +5 -7
  128. package/dist/run-archive.d.ts +1 -1
  129. package/dist/run-archive.js +16 -16
  130. package/dist/run-persistence.js +112 -12
  131. package/dist/tdd-cycle.d.ts +3 -3
  132. package/dist/tdd-cycle.js +1 -1
  133. package/dist/types.d.ts +18 -10
  134. package/package.json +1 -1
  135. package/dist/content/finish-command.d.ts +0 -2
  136. package/dist/content/finish-command.js +0 -26
  137. package/dist/content/ideate-command.d.ts +0 -8
  138. package/dist/content/ideate-frames.d.ts +0 -31
  139. package/dist/content/ideate-ranking.d.ts +0 -25
  140. package/dist/content/next-command.d.ts +0 -20
  141. package/dist/content/next-command.js +0 -298
  142. package/dist/content/seed-shelf.d.ts +0 -36
  143. package/dist/content/seed-shelf.js +0 -301
  144. package/dist/content/stage-common-guidance.d.ts +0 -1
  145. package/dist/content/stage-common-guidance.js +0 -106
  146. package/dist/doctor-registry.d.ts +0 -10
  147. package/dist/doctor-registry.js +0 -186
  148. package/dist/doctor.d.ts +0 -17
  149. package/dist/doctor.js +0 -2206
  150. package/dist/internal/hook-manifest.d.ts +0 -16
  151. package/dist/internal/hook-manifest.js +0 -77
@@ -0,0 +1,170 @@
1
+ import { SHIP_FINALIZATION_MODES } from "../../constants.js";
2
+ import { PASS_STATUS_PATTERN, TEST_COMMAND_HINT_PATTERN, validateTddVerificationEvidence } from "../../tdd-verification-evidence.js";
3
+ import { asRecord } from "./helpers.js";
4
+ export const AUTO_REVIEW_LOOP_GATE_BY_STAGE = {
5
+ design: "design_architecture_locked"
6
+ };
7
+ const SHIP_FINALIZATION_MODE_PATTERN = new RegExp(`\\b(?:${SHIP_FINALIZATION_MODES.join("|")})\\b`, "u");
8
+ const SHIP_FINALIZATION_MODE_HINT = SHIP_FINALIZATION_MODES.join(", ");
9
+ const REVIEW_LOOP_STOP_REASONS = new Set([
10
+ "quality_threshold_met",
11
+ "max_iterations_reached",
12
+ "user_opt_out"
13
+ ]);
14
+ export function pickReviewLoopEnvelope(value) {
15
+ const direct = asRecord(value);
16
+ if (!direct)
17
+ return null;
18
+ if (direct.type === "review-loop")
19
+ return direct;
20
+ const payload = asRecord(direct.payload);
21
+ if (payload?.type === "review-loop")
22
+ return payload;
23
+ const nested = asRecord(direct.reviewLoop);
24
+ if (nested?.type === "review-loop")
25
+ return nested;
26
+ return null;
27
+ }
28
+ export function validateReviewLoopGateEvidence(stage, evidence) {
29
+ let parsed;
30
+ try {
31
+ parsed = JSON.parse(evidence);
32
+ }
33
+ catch {
34
+ return "must be JSON containing a review-loop envelope (`type: \"review-loop\"`) in top-level, `payload`, or `reviewLoop`.";
35
+ }
36
+ const envelope = pickReviewLoopEnvelope(parsed);
37
+ if (!envelope) {
38
+ return "must include a review-loop envelope (`type: \"review-loop\"`) in top-level, `payload`, or `reviewLoop`.";
39
+ }
40
+ if (envelope.stage !== stage) {
41
+ return `review-loop envelope stage must be "${stage}".`;
42
+ }
43
+ const targetScore = envelope.targetScore;
44
+ if (typeof targetScore !== "number" || Number.isNaN(targetScore) || targetScore < 0 || targetScore > 1) {
45
+ return "review-loop targetScore must be a number between 0 and 1.";
46
+ }
47
+ const maxIterations = envelope.maxIterations;
48
+ if (typeof maxIterations !== "number" ||
49
+ Number.isNaN(maxIterations) ||
50
+ !Number.isInteger(maxIterations) ||
51
+ maxIterations < 1) {
52
+ return "review-loop maxIterations must be an integer >= 1.";
53
+ }
54
+ if (typeof envelope.stopReason !== "string" || !REVIEW_LOOP_STOP_REASONS.has(envelope.stopReason)) {
55
+ return "review-loop stopReason must be one of quality_threshold_met, max_iterations_reached, user_opt_out.";
56
+ }
57
+ const rows = envelope.iterations;
58
+ if (!Array.isArray(rows) || rows.length === 0) {
59
+ return "review-loop iterations must be a non-empty array.";
60
+ }
61
+ if (rows.length > maxIterations) {
62
+ return "review-loop iterations count cannot exceed maxIterations.";
63
+ }
64
+ let prevScore = -Infinity;
65
+ let reachedTarget = false;
66
+ for (let index = 0; index < rows.length; index++) {
67
+ const row = asRecord(rows[index]);
68
+ if (!row) {
69
+ return `review-loop iterations[${index}] must be an object.`;
70
+ }
71
+ const iteration = row.iteration;
72
+ const qualityScore = row.qualityScore;
73
+ const findingsCount = row.findingsCount;
74
+ if (typeof iteration !== "number" ||
75
+ Number.isNaN(iteration) ||
76
+ !Number.isInteger(iteration) ||
77
+ iteration < 1) {
78
+ return `review-loop iterations[${index}].iteration must be an integer >= 1.`;
79
+ }
80
+ if (typeof qualityScore !== "number" ||
81
+ Number.isNaN(qualityScore) ||
82
+ qualityScore < 0 ||
83
+ qualityScore > 1) {
84
+ return `review-loop iterations[${index}].qualityScore must be between 0 and 1.`;
85
+ }
86
+ if (typeof findingsCount !== "number" ||
87
+ Number.isNaN(findingsCount) ||
88
+ !Number.isInteger(findingsCount) ||
89
+ findingsCount < 0) {
90
+ return `review-loop iterations[${index}].findingsCount must be an integer >= 0.`;
91
+ }
92
+ if (qualityScore + Number.EPSILON < prevScore) {
93
+ return "review-loop qualityScore must be monotonic non-decreasing across iterations.";
94
+ }
95
+ if (qualityScore >= targetScore) {
96
+ reachedTarget = true;
97
+ }
98
+ prevScore = qualityScore;
99
+ }
100
+ if (envelope.stopReason === "quality_threshold_met" && !reachedTarget) {
101
+ return "review-loop stopReason is quality_threshold_met but no iteration reached targetScore.";
102
+ }
103
+ if (envelope.stopReason === "max_iterations_reached" && rows.length < maxIterations) {
104
+ return "review-loop stopReason is max_iterations_reached but iterations are below maxIterations.";
105
+ }
106
+ return null;
107
+ }
108
+ export function validateUserApprovalEvidence(evidence) {
109
+ const normalized = evidence.trim();
110
+ if (normalized.length === 0) {
111
+ return "must cite explicit user approval.";
112
+ }
113
+ const reviewLoopEnvelope = (() => {
114
+ try {
115
+ return pickReviewLoopEnvelope(JSON.parse(normalized));
116
+ }
117
+ catch {
118
+ return null;
119
+ }
120
+ })();
121
+ if (reviewLoopEnvelope) {
122
+ return "must cite explicit user approval; review-loop evidence is outside-voice evidence, not user approval.";
123
+ }
124
+ if (/\b(?:approved|approval|user approved|confirmed|accepted|yes|ok)\b/iu.test(normalized)) {
125
+ return null;
126
+ }
127
+ if (/\b(?:утвержд(?:аю|ено|ен|ена)|подтвержд(?:аю|ено|ен|ена)|соглас(?:ен|на|овано)|да|ок|принято)\b/iu.test(normalized)) {
128
+ return null;
129
+ }
130
+ return "must cite explicit user approval (for example `user approved the scope contract` or `пользователь утвердил scope`).";
131
+ }
132
+ // Per-gate validators keyed by `${stage}:${gateId}`. Returning a non-null
133
+ // string surfaces the reason as an `advance-stage` failure so evidence is
134
+ // guaranteed to carry the structural breadcrumbs downstream tooling
135
+ // expects. Previously only `tdd:tdd_verified_before_complete` was checked.
136
+ const GATE_EVIDENCE_VALIDATORS = {
137
+ "review:review_trace_matrix_clean": (evidence) => {
138
+ if (!TEST_COMMAND_HINT_PATTERN.test(evidence)) {
139
+ return "must include the fresh verification command that was run before ship handoff (for example `npm test`, `pytest`, `go test`, or equivalent).";
140
+ }
141
+ if (!PASS_STATUS_PATTERN.test(evidence)) {
142
+ return "must include explicit success status (for example `PASS` or `GREEN`).";
143
+ }
144
+ return null;
145
+ },
146
+ "ship:ship_finalization_executed": (evidence) => {
147
+ if (!SHIP_FINALIZATION_MODE_PATTERN.test(evidence)) {
148
+ return `must name the finalization mode that ran (for example ${SHIP_FINALIZATION_MODE_HINT}).`;
149
+ }
150
+ return null;
151
+ },
152
+ "scope:scope_user_approved": (evidence) => validateUserApprovalEvidence(evidence),
153
+ "design:design_architecture_locked": (evidence) => validateReviewLoopGateEvidence("design", evidence)
154
+ };
155
+ export async function validateGateEvidenceShape(projectRoot, stage, gateId, evidence) {
156
+ const normalized = evidence.trim();
157
+ if (stage === "tdd" && gateId === "tdd_verified_before_complete") {
158
+ const result = await validateTddVerificationEvidence(projectRoot, normalized);
159
+ return result.ok ? null : result.issues.join(" ");
160
+ }
161
+ const validator = GATE_EVIDENCE_VALIDATORS[`${stage}:${gateId}`];
162
+ if (!validator)
163
+ return null;
164
+ return validator(normalized);
165
+ }
166
+ export function reviewLoopArtifactFixHint(stage, gateId) {
167
+ if (AUTO_REVIEW_LOOP_GATE_BY_STAGE[stage] !== gateId)
168
+ return "";
169
+ return ` Add a \`## ${stage === "scope" ? "Scope Outside Voice Loop" : "Design Outside Voice Loop"}\` table to the artifact with rows like \`| 1 | 0.80 | 0 |\` plus \`- Stop reason: quality_threshold_met\`, \`- Target score: 0.80\`, and \`- Max iterations: 3\`; then omit this gate from manual evidence so stage-complete can auto-hydrate it.`;
170
+ }
@@ -0,0 +1,14 @@
1
+ import { type FlowState } from "../../flow-state.js";
2
+ import { type FlowStage } from "../../types.js";
3
+ import type { RewindArgs } from "./parsers.js";
4
+ import type { Writable } from "node:stream";
5
+ interface InternalIo {
6
+ stdout: Writable;
7
+ stderr: Writable;
8
+ }
9
+ export declare function rewindLogPath(projectRoot: string): string;
10
+ export declare function rewindId(date?: Date): string;
11
+ export declare function stagesInvalidatedByRewind(current: FlowState, targetStage: FlowStage): FlowStage[];
12
+ export declare function appendRewindLog(projectRoot: string, payload: Record<string, unknown>): Promise<void>;
13
+ export declare function runRewind(projectRoot: string, args: RewindArgs, io: InternalIo): Promise<number>;
14
+ export {};
@@ -0,0 +1,108 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { RUNTIME_ROOT } from "../../constants.js";
4
+ import { ensureDir } from "../../fs-utils.js";
5
+ import { readFlowState, writeFlowState } from "../../runs.js";
6
+ import { TRACK_STAGES } from "../../types.js";
7
+ export function rewindLogPath(projectRoot) {
8
+ return path.join(projectRoot, RUNTIME_ROOT, "state", "rewind-log.jsonl");
9
+ }
10
+ export function rewindId(date = new Date()) {
11
+ return `rewind-${date.getTime().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
12
+ }
13
+ export function stagesInvalidatedByRewind(current, targetStage) {
14
+ const ordered = TRACK_STAGES[current.track];
15
+ const targetIndex = ordered.indexOf(targetStage);
16
+ const currentIndex = ordered.indexOf(current.currentStage);
17
+ if (targetIndex < 0 || currentIndex < 0 || targetIndex > currentIndex) {
18
+ return [];
19
+ }
20
+ return ordered.slice(targetIndex, currentIndex + 1);
21
+ }
22
+ export async function appendRewindLog(projectRoot, payload) {
23
+ const logPath = rewindLogPath(projectRoot);
24
+ await ensureDir(path.dirname(logPath));
25
+ await fs.appendFile(logPath, `${JSON.stringify(payload)}\n`, "utf8");
26
+ }
27
+ export async function runRewind(projectRoot, args, io) {
28
+ const current = await readFlowState(projectRoot);
29
+ const now = new Date().toISOString();
30
+ if (args.mode === "ack") {
31
+ const marker = current.staleStages[args.targetStage];
32
+ if (!marker) {
33
+ io.stderr.write(`cclaw internal rewind: no stale marker exists for "${args.targetStage}".\n`);
34
+ return 1;
35
+ }
36
+ if (current.currentStage !== args.targetStage) {
37
+ io.stderr.write(`cclaw internal rewind: cannot ack "${args.targetStage}" while currentStage is "${current.currentStage}". Re-run the stale stage before acknowledging it.\n`);
38
+ return 1;
39
+ }
40
+ const staleStages = { ...current.staleStages };
41
+ delete staleStages[args.targetStage];
42
+ const nextState = { ...current, staleStages };
43
+ await writeFlowState(projectRoot, nextState);
44
+ const payload = {
45
+ ok: true,
46
+ command: "rewind",
47
+ action: "ack",
48
+ stage: args.targetStage,
49
+ acknowledgedAt: now,
50
+ rewindId: marker.rewindId
51
+ };
52
+ await appendRewindLog(projectRoot, payload);
53
+ if (!args.quiet) {
54
+ io.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
55
+ }
56
+ return 0;
57
+ }
58
+ const invalidatedStages = stagesInvalidatedByRewind(current, args.targetStage);
59
+ if (invalidatedStages.length === 0) {
60
+ io.stderr.write(`cclaw internal rewind: target "${args.targetStage}" is not an earlier or current stage on track "${current.track}" from "${current.currentStage}".\n`);
61
+ return 1;
62
+ }
63
+ const id = rewindId();
64
+ const completedInvalidated = new Set(invalidatedStages);
65
+ const staleStages = { ...current.staleStages };
66
+ for (const stage of invalidatedStages) {
67
+ staleStages[stage] = {
68
+ rewindId: id,
69
+ reason: args.reason ?? "rewind",
70
+ markedAt: now
71
+ };
72
+ }
73
+ const record = {
74
+ id,
75
+ fromStage: current.currentStage,
76
+ toStage: args.targetStage,
77
+ reason: args.reason ?? "rewind",
78
+ timestamp: now,
79
+ invalidatedStages
80
+ };
81
+ const nextState = {
82
+ ...current,
83
+ currentStage: args.targetStage,
84
+ completedStages: current.completedStages.filter((stage) => !completedInvalidated.has(stage)),
85
+ staleStages,
86
+ rewinds: [...current.rewinds, record]
87
+ };
88
+ await writeFlowState(projectRoot, nextState);
89
+ const payload = {
90
+ ok: true,
91
+ command: "rewind",
92
+ action: "rewind",
93
+ rewind: record,
94
+ currentStage: nextState.currentStage,
95
+ completedStages: nextState.completedStages,
96
+ staleStages: Object.keys(nextState.staleStages),
97
+ nextActions: [
98
+ `Re-run ${args.targetStage} stage work and update its artifact evidence.`,
99
+ `Then run cclaw internal rewind --ack ${args.targetStage}.`,
100
+ "Continue with /cc after the stale marker is acknowledged."
101
+ ]
102
+ };
103
+ await appendRewindLog(projectRoot, payload);
104
+ if (!args.quiet) {
105
+ io.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
106
+ }
107
+ return 0;
108
+ }
@@ -0,0 +1,11 @@
1
+ import { type FlowState } from "../../flow-state.js";
2
+ import type { StartFlowArgs } from "./parsers.js";
3
+ import type { Writable } from "node:stream";
4
+ interface InternalIo {
5
+ stdout: Writable;
6
+ stderr: Writable;
7
+ }
8
+ export declare function discoverStartFlowContext(projectRoot: string): Promise<string[]>;
9
+ export declare function appendIdeaArtifact(projectRoot: string, args: StartFlowArgs, previous?: FlowState): Promise<void>;
10
+ export declare function runStartFlow(projectRoot: string, args: StartFlowArgs, io: InternalIo): Promise<number>;
11
+ export {};
@@ -0,0 +1,136 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { RUNTIME_ROOT } from "../../constants.js";
4
+ import { createInitialFlowState } from "../../flow-state.js";
5
+ import { readFlowState, writeFlowState } from "../../runs.js";
6
+ import { listExistingFiles, listFilesUnder, pathExists } from "./helpers.js";
7
+ import { TRACK_STAGES } from "../../types.js";
8
+ import { buildValidationReport } from "./advance.js";
9
+ import { carriedCompletedStageCatalog, completedStageClosureEvidenceIssues, firstIncompleteStageForTrack } from "./verify.js";
10
+ export async function discoverStartFlowContext(projectRoot) {
11
+ const lines = [];
12
+ const seedFiles = (await listFilesUnder(projectRoot, path.join(RUNTIME_ROOT, "seeds"), 10))
13
+ .filter((relPath) => /^\.cclaw\/seeds\/SEED-.*\.md$/u.test(relPath));
14
+ lines.push(seedFiles.length > 0
15
+ ? `- Seed shelf scanned: ${seedFiles.join(", ")}.`
16
+ : "- Seed shelf scanned: no `.cclaw/seeds/SEED-*.md` files found.");
17
+ const originDirs = ["docs/prd", "docs/rfcs", "docs/adr", "docs/design", "specs", "prd", "rfc", "design"];
18
+ const originRootFiles = ["PRD.md", "SPEC.md", "DESIGN.md", "REQUIREMENTS.md", "ROADMAP.md"];
19
+ const originFiles = [
20
+ ...(await listExistingFiles(projectRoot, originRootFiles)),
21
+ ...(await Promise.all(originDirs.map((dir) => listFilesUnder(projectRoot, dir, 6)))).flat()
22
+ ].slice(0, 20);
23
+ lines.push(originFiles.length > 0
24
+ ? `- Origin docs scanned: found ${originFiles.join(", ")}.`
25
+ : "- Origin docs scanned: no PRD/RFC/ADR/design/spec files found in configured locations.");
26
+ const stackMarkers = await listExistingFiles(projectRoot, [
27
+ "package.json",
28
+ "pyproject.toml",
29
+ "requirements.txt",
30
+ "requirements-dev.txt",
31
+ ".python-version",
32
+ "go.mod",
33
+ "Cargo.toml",
34
+ "pom.xml",
35
+ "build.gradle",
36
+ "build.gradle.kts",
37
+ "Dockerfile",
38
+ "docker-compose.yml",
39
+ "docker-compose.yaml",
40
+ ".gitlab-ci.yml"
41
+ ]);
42
+ if (await pathExists(projectRoot, ".github/workflows")) {
43
+ stackMarkers.push(".github/workflows/");
44
+ }
45
+ lines.push(stackMarkers.length > 0
46
+ ? `- Stack markers scanned: found ${stackMarkers.join(", ")}.`
47
+ : "- Stack markers scanned: no root stack markers found.");
48
+ return lines;
49
+ }
50
+ export async function appendIdeaArtifact(projectRoot, args, previous) {
51
+ const artifactPath = path.join(projectRoot, RUNTIME_ROOT, "artifacts", "00-idea.md");
52
+ await fs.mkdir(path.dirname(artifactPath), { recursive: true });
53
+ const now = new Date().toISOString();
54
+ if (args.reclassify) {
55
+ const entry = [
56
+ "",
57
+ `Reclassification: ${now}`,
58
+ `- From: ${previous?.track ?? "unknown"}`,
59
+ `- To: ${args.track}`,
60
+ `- Class: ${args.className || "unspecified"}`,
61
+ `- Reason: ${args.reason || "unspecified"}`
62
+ ].join("\n") + "\n";
63
+ await fs.appendFile(artifactPath, entry, "utf8");
64
+ return;
65
+ }
66
+ const discoveredContext = await discoverStartFlowContext(projectRoot);
67
+ const body = [
68
+ "# Idea",
69
+ `Class: ${args.className || "unspecified"}`,
70
+ `Track: ${args.track}${args.reason ? ` (${args.reason})` : ""}`,
71
+ `Stack: ${args.stack || "unknown"}`,
72
+ "",
73
+ "## User prompt",
74
+ args.prompt || "(not provided)",
75
+ "",
76
+ "## Discovered context",
77
+ ...discoveredContext
78
+ ].join("\n") + "\n";
79
+ await fs.writeFile(artifactPath, body, "utf8");
80
+ }
81
+ export async function runStartFlow(projectRoot, args, io) {
82
+ const current = await readFlowState(projectRoot);
83
+ const hasProgress = current.completedStages.length > 0;
84
+ if (!args.reclassify && hasProgress && !args.forceReset) {
85
+ io.stderr.write("cclaw internal start-flow: refusing to reset an active flow with completed stages without --force-reset. Ask the user before resetting.\n");
86
+ return 1;
87
+ }
88
+ let nextState;
89
+ if (args.reclassify) {
90
+ const completedInNewTrack = current.completedStages.filter((stage) => TRACK_STAGES[args.track].includes(stage));
91
+ const fresh = createInitialFlowState({ activeRunId: current.activeRunId, track: args.track });
92
+ const stageGateCatalog = { ...fresh.stageGateCatalog };
93
+ const guardEvidence = {};
94
+ for (const stage of completedInNewTrack) {
95
+ const carried = carriedCompletedStageCatalog(current, fresh, stage);
96
+ stageGateCatalog[stage] = carried.catalog;
97
+ Object.assign(guardEvidence, carried.evidence);
98
+ }
99
+ nextState = {
100
+ ...fresh,
101
+ completedStages: completedInNewTrack,
102
+ currentStage: firstIncompleteStageForTrack(args.track, completedInNewTrack),
103
+ guardEvidence,
104
+ stageGateCatalog,
105
+ rewinds: current.rewinds,
106
+ staleStages: current.staleStages
107
+ };
108
+ const validation = await buildValidationReport(projectRoot, nextState);
109
+ const evidenceIssues = completedStageClosureEvidenceIssues(nextState);
110
+ if (!validation.completedStages.ok || evidenceIssues.length > 0) {
111
+ io.stderr.write("cclaw internal start-flow: reclassification would leave completed stages without valid gate closure.\n");
112
+ const issues = [...validation.completedStages.issues, ...evidenceIssues];
113
+ if (issues.length > 0) {
114
+ io.stderr.write(`- completed-stage closure issues: ${issues.join(" | ")}\n`);
115
+ }
116
+ return 1;
117
+ }
118
+ }
119
+ else {
120
+ nextState = createInitialFlowState({ track: args.track });
121
+ }
122
+ await writeFlowState(projectRoot, nextState, { allowReset: true });
123
+ await appendIdeaArtifact(projectRoot, args, current);
124
+ if (!args.quiet) {
125
+ io.stdout.write(`${JSON.stringify({
126
+ ok: true,
127
+ command: "start-flow",
128
+ reclassify: args.reclassify,
129
+ track: nextState.track,
130
+ currentStage: nextState.currentStage,
131
+ skippedStages: nextState.skippedStages,
132
+ activeRunId: nextState.activeRunId
133
+ }, null, 2)}\n`);
134
+ }
135
+ return 0;
136
+ }
@@ -0,0 +1,29 @@
1
+ import { type StageAutoSubagentDispatch } from "../../content/stage-schema.js";
2
+ import { type FlowState, type StageGateState } from "../../flow-state.js";
3
+ import { type FlowStage, type FlowTrack } from "../../types.js";
4
+ import type { VerifyCurrentStateArgs, VerifyFlowStateDiffArgs } from "./parsers.js";
5
+ import type { Writable } from "node:stream";
6
+ interface InternalIo {
7
+ stdout: Writable;
8
+ stderr: Writable;
9
+ }
10
+ interface ProactiveDelegationTraceResult {
11
+ missingRules: StageAutoSubagentDispatch[];
12
+ }
13
+ export declare function runVerifyFlowStateDiff(projectRoot: string, args: VerifyFlowStateDiffArgs, io: InternalIo): Promise<number>;
14
+ export declare function runVerifyCurrentState(projectRoot: string, args: VerifyCurrentStateArgs, io: InternalIo): Promise<number>;
15
+ export declare function firstIncompleteStageForTrack(track: FlowTrack, completedStages: FlowStage[]): FlowStage;
16
+ export declare function carriedCompletedStageCatalog(current: FlowState, fresh: FlowState, stage: FlowStage): {
17
+ catalog: StageGateState;
18
+ evidence: Record<string, string>;
19
+ };
20
+ export declare function completedStageClosureEvidenceIssues(flowState: FlowState): string[];
21
+ export declare function ensureProactiveDelegationTrace(projectRoot: string, stage: FlowStage, options: {
22
+ acceptWaiver: boolean;
23
+ waiverReason?: string;
24
+ }): Promise<ProactiveDelegationTraceResult>;
25
+ export declare function pathExists(projectRoot: string, relPath: string): Promise<boolean>;
26
+ export declare function listExistingFiles(projectRoot: string, relPaths: string[]): Promise<string[]>;
27
+ export declare function listFilesUnder(projectRoot: string, relDir: string, limit?: number): Promise<string[]>;
28
+ export declare function discoverStartFlowContext(projectRoot: string): Promise<string[]>;
29
+ export {};