cclaw-cli 7.5.0 → 7.7.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 +2 -1
- package/dist/artifact-linter/plan.js +238 -26
- package/dist/artifact-linter/tdd.js +4 -3
- package/dist/config.d.ts +18 -1
- package/dist/config.js +176 -5
- package/dist/content/core-agents.d.ts +1 -1
- package/dist/content/core-agents.js +17 -2
- package/dist/content/hooks.js +37 -0
- package/dist/content/meta-skill.js +4 -4
- package/dist/content/skills.js +12 -8
- package/dist/content/stage-schema.js +3 -2
- package/dist/content/stages/plan.js +18 -17
- package/dist/content/stages/tdd.js +13 -10
- package/dist/content/start-command.js +3 -3
- package/dist/content/subagent-context-skills.js +2 -2
- package/dist/content/subagents.js +6 -6
- package/dist/content/templates.js +12 -7
- package/dist/delegation.d.ts +43 -3
- package/dist/delegation.js +80 -9
- package/dist/execution-topology.d.ts +36 -0
- package/dist/execution-topology.js +73 -0
- package/dist/gate-evidence.js +10 -12
- package/dist/internal/advance-stage/start-flow.js +13 -4
- package/dist/internal/cohesion-contract-stub.js +2 -14
- package/dist/internal/plan-split-waves.d.ts +5 -2
- package/dist/internal/plan-split-waves.js +27 -16
- package/dist/internal/slice-commit.js +161 -7
- package/dist/internal/wave-status.d.ts +4 -0
- package/dist/internal/wave-status.js +50 -9
- package/dist/stack-detection.d.ts +94 -0
- package/dist/stack-detection.js +431 -0
- package/dist/tdd-cycle.js +7 -5
- package/dist/types.d.ts +67 -0
- package/dist/util/slice-id.d.ts +58 -0
- package/dist/util/slice-id.js +89 -0
- package/package.json +1 -1
|
@@ -8,7 +8,7 @@ export const TDD = {
|
|
|
8
8
|
complexityTier: "standard",
|
|
9
9
|
skillFolder: "tdd",
|
|
10
10
|
skillName: "tdd",
|
|
11
|
-
skillDescription: "
|
|
11
|
+
skillDescription: "Adaptive vertical-slice TDD cycle: route each feature-atomic implementation unit through inline, single-builder, parallel-builder, or strict-micro execution while preserving RED-first traceability.",
|
|
12
12
|
philosophy: {
|
|
13
13
|
hardGate: "Do NOT merge, ship, or skip review. Follow RED → GREEN → REFACTOR strictly for each plan slice. Do NOT write implementation code before RED tests exist. Do NOT write RED tests before discovering relevant existing tests and impacted contracts. Do NOT skip the REFACTOR step.",
|
|
14
14
|
ironLaw: "NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST — THE RED FAILURE IS THE SPEC.",
|
|
@@ -37,19 +37,21 @@ export const TDD = {
|
|
|
37
37
|
},
|
|
38
38
|
executionModel: {
|
|
39
39
|
checklist: [
|
|
40
|
-
"**Wave discovery:** Entering TDD, first call `node .cclaw/cli.mjs internal wave-status --json`. It parses the managed `<!-- parallel-exec-managed-start -->` block
|
|
41
|
-
"**
|
|
40
|
+
"**Wave discovery:** Entering TDD, first call `node .cclaw/cli.mjs internal wave-status --json`. It parses the managed `<!-- parallel-exec-managed-start -->` block and reports the adaptive `topology`; read `05-plan.md`/`wave-plans/` only once `wave-status` names work. Restore partial waves by resuming remaining units.",
|
|
41
|
+
"**Topology routing:** Default `auto` + `balanced` means cheapest safe route: inline for one low-risk inline-safe unit, single-builder for one feature-atomic unit or conflicts, parallel-builders only for genuinely independent substantial units, strict-micro for high-risk/configured micro-slice work.",
|
|
42
|
+
"**Routing AskQuestion:** Two or more ready units with `topology=parallel-builders` ⇒ exactly one AskQuestion (“launch wave …” vs “single builder …”, default wave). Otherwise do not ask “which slice next?” when the plan already resolves it.",
|
|
42
43
|
"**Record before dispatch:** For every `Task`, write `delegation-record` `--status=scheduled` then `--status=launched` before the tool call. Workers self-record `acknowledged` and `completed`; back-fill is `--repair` only.",
|
|
43
|
-
"**One worker per
|
|
44
|
-
"**
|
|
45
|
-
"**
|
|
44
|
+
"**One worker per feature-atomic unit when delegated:** Dispatch `slice-builder` with `--slice S-<id>` and explicit `--paths` from the plan. The worker owns the unit's internal 2-5 minute RED/GREEN/REFACTOR steps. Parallel builders are allowed only when topology says `parallel-builders` and paths are disjoint; honor any lane/lease flags the hook requires today.",
|
|
45
|
+
"**Inline topology:** When the router/guidance chooses `inline`, the controller may execute the unit without a builder dispatch, but must still keep RED-before-GREEN, AC traceability, path containment, verification-before-completion, lockfile twin handling, managed commit/worktree policy, and orphan-change gates intact.",
|
|
46
|
+
"**Single span owns the delegated unit:** `slice-builder` runs RED → GREEN → REFACTOR (separate phase rows or `--refactor-outcome` on GREEN) and authors `<artifacts-dir>/tdd-slices/S-<id>.md`. Follow the agent body and `delegation-record` snippets it embeds.",
|
|
47
|
+
"**Wave closure:** When every slice/unit in the wave has GREEN + REFACTOR coverage, call `integrationCheckRequired`. Dispatch `integration-overseer` when required; otherwise emit `cclaw_integration_overseer_skipped` via `delegation-record --audit-kind=...`.",
|
|
46
48
|
"**Plan triggers:** If the unit row demands extra scrutiny (`touchCount >= filesChangedThreshold`, matching `touchPaths`, or `highRisk`), capture that review posture in `tdd-slices/S-<id>.md` or via a reviewer dispatch before closing the slice.",
|
|
47
49
|
"**Auto-render tables:** Do not hand-edit content between `auto-start: tdd-slice-summary` markers; the linter overwrites them from `delegation-events.jsonl`.",
|
|
48
50
|
"**Active-span collisions:** If scheduling fails with `dispatch_duplicate` / `dispatch_active_span_collision`, identify the live span; use `--allow-parallel` or `--supersede` deliberately. Do not silence errors blindly.",
|
|
49
51
|
],
|
|
50
52
|
interactionProtocol: [
|
|
51
|
-
"Parallel `slice-builder` tasks are allowed when `claimedPaths` are disjoint; remain serial
|
|
52
|
-
"Controller
|
|
53
|
+
"Parallel `slice-builder` tasks are allowed only when topology is `parallel-builders` and `claimedPaths` are disjoint; remain serial for dependencies, conflicts, high-risk units, or strict-micro chains.",
|
|
54
|
+
"Controller normally orchestrates delegated builders. In `inline` topology it may execute directly, but must record the routing decision and satisfy the same RED/GREEN/REFACTOR evidence gates before completion.",
|
|
53
55
|
"Discover existing tests and commands before RED; run a system-wide impact check (callbacks, state, interfaces, contracts) before GREEN.",
|
|
54
56
|
"RED must fail for the right reason; capture logs. GREEN must run the full relevant suite, not a narrow subset.",
|
|
55
57
|
"Before calling a slice done, run verification-before-completion (command + PASS/FAIL + durable commit evidence: managed-per-slice git commits when `.git` is present, or explicit no-VCS attestation + hash).",
|
|
@@ -59,8 +61,9 @@ export const TDD = {
|
|
|
59
61
|
process: [
|
|
60
62
|
"Map the slice to acceptance criteria; read `ralph-loop.json` for open RED cycles before starting new work.",
|
|
61
63
|
"Discover tests, fixtures, helpers, and commands; record impact on public surfaces.",
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
+
"Route the active unit via topology (`inline`, `single-builder`, `parallel-builders`, or `strict-micro`) before RED.",
|
|
65
|
+
"For delegated units, dispatch `slice-builder` for RED (failing tests, no production edits beyond test files).",
|
|
66
|
+
"For delegated units, dispatch the same builder for GREEN with minimal production changes and full-suite evidence.",
|
|
64
67
|
"Close REFACTOR inline, via deferred phase, or `--refactor-outcome` on GREEN — match what `delegation-record` expects.",
|
|
65
68
|
"Keep `tdd-slices/S-<id>.md` aligned with evidence as the builder finishes.",
|
|
66
69
|
"Run fresh verification and attach traceability (plan task + spec criterion).",
|
|
@@ -117,9 +117,9 @@ If during any stage the agent discovers evidence that contradicts the initial Ph
|
|
|
117
117
|
4. Otherwise check current stage gates, resume if incomplete, and advance if complete.
|
|
118
118
|
5. **TDD wave dispatch:** When \`currentStage\` is \`tdd\`, run \`node .cclaw/cli.mjs internal wave-status --json\` first, then read the managed **Parallel Execution Plan** block inside \`${RUNTIME_ROOT}/artifacts/05-plan.md\` plus \`${RUNTIME_ROOT}/artifacts/wave-plans/\` for detail. Resume partial waves on remaining members only.
|
|
119
119
|
|
|
120
|
-
**The controller
|
|
120
|
+
**The controller preserves TDD evidence.** When \`topology: parallel-builders\` and \`pathConflicts: []\`, fan out the ready independent units in a SINGLE controller message: one harness \`Task(subagent_type=…, description="slice-builder S-<id>", prompt=<full slice context>)\` call per routed builder, **side by side in the same tool batch**. Each \`slice-builder\` span owns the full RED → GREEN → REFACTOR → DOC cycle for its feature-atomic unit and emits its own \`delegation-record --phase=red|green|refactor|refactor-deferred|doc\` rows. RED-before-GREEN is enforced per-slice by the linter.
|
|
121
121
|
|
|
122
|
-
When \`mode: blocked\` with \`pathConflicts\`, surface exactly one AskQuestion that lets the user resolve the overlap (drop / split / serialize). When \`
|
|
122
|
+
When \`mode: blocked\` with \`pathConflicts\`, surface exactly one AskQuestion that lets the user resolve the overlap (drop / split / serialize). When \`topology: single-builder\`, dispatch one \`Task\` for the next ready unit. When \`topology: inline\`, execute inline only if the same RED/GREEN/REFACTOR evidence and path/commit gates can be satisfied.
|
|
123
123
|
|
|
124
124
|
6. **Auto-advance after stage-complete:** when \`stage-complete\` returns \`ok\` with a new \`currentStage\`, immediately load the next stage skill and continue without waiting for the user to retype \`/cc\`. Announce \`Stage <prev> complete → entering <next>. Continuing.\` and proceed.
|
|
125
125
|
|
|
@@ -214,7 +214,7 @@ Progress the tracked flow only when one exists:
|
|
|
214
214
|
2. If missing, guide the user to run \`npx cclaw-cli init\` and stop.
|
|
215
215
|
3. If it is only a fresh init placeholder (\`completedStages: []\`, no passed gates, and no \`${RUNTIME_ROOT}/artifacts/00-idea.md\`), stop and ask for \`/cc <prompt>\` to start a tracked run. Do not silently create a brainstorm run.
|
|
216
216
|
4. Check gates for \`currentStage\`.
|
|
217
|
-
5. **TDD:** When \`currentStage\` is \`tdd\`, run \`wave-status --json\`, then reconcile the managed **Parallel Execution Plan** in \`05-plan.md\` with \`wave-plans/wave-NN.md\`.
|
|
217
|
+
5. **TDD:** When \`currentStage\` is \`tdd\`, run \`wave-status --json\`, then reconcile the managed **Parallel Execution Plan** in \`05-plan.md\` with \`wave-plans/wave-NN.md\`. Route by \`nextDispatch.topology\`: \`parallel-builders\` fans out independent units in one controller message, \`single-builder\` dispatches one builder, \`inline\` executes directly only with equivalent evidence, and \`strict-micro\` preserves tiny-slice sequencing. If \`mode: blocked\`, resolve overlaps first. Each delegated \`slice-builder\` span owns its full RED → GREEN → REFACTOR → DOC cycle. Mirror plan \`dependsOn\` ordering between waves.
|
|
218
218
|
6. **Wave resume:** Parallelize unfinished members; never restart completed lanes. Integration-overseer follows \`integrationCheckRequired\`; when skipped, emit \`cclaw_integration_overseer_skipped\` per the hook contract.
|
|
219
219
|
7. If incomplete → load current stage skill and execute.
|
|
220
220
|
8. If complete → advance to next stage and execute. **Auto-advance:** when \`stage-complete\` returns \`ok\`, immediately load the next stage skill and continue without waiting for the user to retype \`/cc\`.
|
|
@@ -10,7 +10,7 @@ function skillFrontmatter(name, description) {
|
|
|
10
10
|
function tddCycleEvidenceSkill() {
|
|
11
11
|
return `${skillFrontmatter("tdd-cycle-evidence", "Evidence contract for the mandatory slice-builder delegation across RED/GREEN/REFACTOR/DOC.")}# TDD Cycle Evidence
|
|
12
12
|
|
|
13
|
-
Use with the \`slice-builder\` delegation in the \`tdd\` stage. One \`slice-builder\` span owns the full cycle for a
|
|
13
|
+
Use with the \`slice-builder\` delegation in the \`tdd\` stage. One \`slice-builder\` span owns the full cycle for a feature-atomic implementation unit/slice, including its internal 2-5 minute TDD steps.
|
|
14
14
|
|
|
15
15
|
## Required Output
|
|
16
16
|
|
|
@@ -25,7 +25,7 @@ Use with the \`slice-builder\` delegation in the \`tdd\` stage. One \`slice-buil
|
|
|
25
25
|
- No production code before RED evidence exists.
|
|
26
26
|
- If a RED test cannot be expressed, stop and route back to design/spec with the blocker.
|
|
27
27
|
- Record command output summaries, not just "tests passed".
|
|
28
|
-
- Multiple \`slice-builder\` spans run in parallel inside one wave only when their \`claimedPaths\` are disjoint.
|
|
28
|
+
- Multiple \`slice-builder\` spans run in parallel inside one wave only when topology is \`parallel-builders\` and their \`claimedPaths\` are disjoint. \`strict-micro\` keeps tiny tasks separate; \`inline\` skips delegation but not RED/GREEN/REFACTOR evidence.
|
|
29
29
|
`;
|
|
30
30
|
}
|
|
31
31
|
function reviewSpecPassSkill() {
|
|
@@ -37,7 +37,7 @@ description: "Orchestrate implementation via isolated subagents — one fresh ag
|
|
|
37
37
|
Use a **controller -> coder -> overseer** loop when building multi-step software work.
|
|
38
38
|
|
|
39
39
|
- **Controller (parent agent):** owns the plan, gating, sequencing, and dispatch decisions; never mixes deep implementation context with review evidence.
|
|
40
|
-
- **Coder / slice-builder (subagent):** receives a **single
|
|
40
|
+
- **Coder / slice-builder (subagent):** receives a **single feature-atomic implementation unit/slice** and edits code only within that scope; exits with a structured status contract.
|
|
41
41
|
- **Overseer / reviewer (subagent):** validates outputs against the specification **by reading code** and never edits during the overseer pass.
|
|
42
42
|
|
|
43
43
|
This pattern is intentionally **Superpowers-style**: cheap parallelism where it doesn’t corrupt state, strict serialization where it would.
|
|
@@ -62,7 +62,7 @@ Reconcile findings into \`.cclaw/artifacts/07-review-army.json\` with explicit s
|
|
|
62
62
|
|
|
63
63
|
### TDD evidence protocol
|
|
64
64
|
|
|
65
|
-
Each
|
|
65
|
+
Each delegated feature-atomic slice runs end-to-end inside one \`slice-builder\` delegation, with RED, GREEN, REFACTOR, and per-slice DOC as phase intents on a single span. The slice may contain internal 2-5 minute TDD steps; \`strict-micro\` is the explicit mode for one tiny task per slice:
|
|
66
66
|
|
|
67
67
|
- \`--phase red\`: failing tests only, no production writes
|
|
68
68
|
- \`--phase green\`: minimal production implementation that passes the matching RED
|
|
@@ -70,7 +70,7 @@ Each parallel slice runs end-to-end inside one \`slice-builder\` delegation, wit
|
|
|
70
70
|
- \`--phase doc\`: write \`<artifacts-dir>/tdd-slices/S-<id>.md\` for the slice
|
|
71
71
|
|
|
72
72
|
Set \`CCLAW_ACTIVE_AGENT\` to the active phase name when possible so workflow-guard
|
|
73
|
-
can enforce phase-appropriate write boundaries. The mandatory gate is the evidence-backed \`slice-builder\` row, not multiple default subagents.
|
|
73
|
+
can enforce phase-appropriate write boundaries. The mandatory gate is the evidence-backed \`slice-builder\` row for delegated topologies, not multiple default subagents. When TDD topology is \`inline\`, the controller may skip the builder but must preserve the same RED-before-GREEN, AC traceability, path containment, verification, managed commit/worktree, lockfile twin, and orphan-change gates.
|
|
74
74
|
|
|
75
75
|
## Model & Harness Routing Notes
|
|
76
76
|
|
|
@@ -154,7 +154,7 @@ If you catch yourself writing “read PLAN.md Task 3” or “implement the next
|
|
|
154
154
|
Borrow the good part of Team/Ruflo-style orchestration without adding a swarm runtime:
|
|
155
155
|
|
|
156
156
|
- **One controller owns alignment.** The parent keeps the task list, gate state, and final synthesis.
|
|
157
|
-
- **Small fan-out by
|
|
157
|
+
- **Small fan-out by topology.** Run at most configured \`maxBuilders\` parallel builders, and only for independent substantial units or independent read-only research/review lenses.
|
|
158
158
|
- **No parallel writes to adjacent surfaces.** If tasks may touch the same module, serialize them.
|
|
159
159
|
- **Checkpoint before synthesis.** Each agent returns status, files inspected/changed, evidence, and blockers before the parent acts.
|
|
160
160
|
- **Consensus is for hard calls only.** Use two reviewers when severity or architecture is disputed; otherwise one evidence-backed reviewer is enough.
|
|
@@ -177,7 +177,7 @@ Before parallel dispatch, answer yes to all gates: tasks are independent, write
|
|
|
177
177
|
- Copy each task verbatim into a working queue (checklist is fine).
|
|
178
178
|
- Normalize each task so it includes: goal, acceptance criteria, constraints, and explicit “out of scope.”
|
|
179
179
|
|
|
180
|
-
2. **For each
|
|
180
|
+
2. **For each unit — cheapest safe topology first; parallel only with cohesion controls:**
|
|
181
181
|
- Implementation subagents are sequential by default. Parallel implementers
|
|
182
182
|
are allowed only when ALL three conditions hold:
|
|
183
183
|
- (a) the lanes touch non-overlapping files (verify via the plan's task
|
|
@@ -397,7 +397,7 @@ This document bridges **Superpowers-style task isolation** with the **gstack “
|
|
|
397
397
|
${conversationLanguagePolicyMarkdown()}
|
|
398
398
|
**Default rule:** parallel implementation agents are SAFE for investigation, analysis, review, and the **TDD wave-fanout** path; otherwise keep implementation sequential.
|
|
399
399
|
|
|
400
|
-
**TDD wave-fanout is the supported parallel-implementation path.** When \`cclaw-cli internal wave-status --json\` reports \`
|
|
400
|
+
**TDD wave-fanout is the supported parallel-implementation path.** When \`cclaw-cli internal wave-status --json\` reports \`topology: parallel-builders\` and \`pathConflicts: []\`, fan out one \`slice-builder\` span per ready feature-atomic unit/slice in a single controller message, capped by \`maxBuilders\`. Each span owns its disjoint \`claimedPaths\`; the dispatcher rejects overlapping spans with \`DispatchOverlapError\` inline, so the disjoint-paths invariant is enforced for you.
|
|
401
401
|
|
|
402
402
|
**Outside wave-fanout** — for ad-hoc parallel implementation that touches shared source trees — keep work **sequential** unless you have proven disjoint filesystem ownership and an explicit merge protocol.
|
|
403
403
|
|
|
@@ -868,7 +868,7 @@ Execution rule: complete and verify each batch before starting the next batch.
|
|
|
868
868
|
## Task List
|
|
869
869
|
|
|
870
870
|
**Rules (apply before writing rows):**
|
|
871
|
-
-
|
|
871
|
+
- Task rows are internal TDD steps unless \`Execution Topology\` is \`strict-micro\`. Keep each step in the **2-5 minute budget**; group related steps into feature-atomic \`U-*\` Implementation Units.
|
|
872
872
|
- **No placeholders.** Forbidden tokens anywhere in this table: \`TODO\`, \`TBD\`, \`FIXME\`, \`<fill-in>\`, \`<your-*-here>\`, \`xxx\`, bare ellipsis. Every file path, test, and verification command must be copy-pasteable as written.
|
|
873
873
|
- **No silent scope reduction.** Forbidden phrasing when locked decisions exist: \`v1\`, \`for now\`, \`later\`, \`temporary\`, \`placeholder\`, \`mock for now\`, \`hardcoded for now\`, \`will improve later\`.
|
|
874
874
|
- If an estimate is genuinely uncertain (new library, unfamiliar subsystem), add a **spike task in batch 0** to de-risk — do NOT hide the uncertainty inside a large estimate.
|
|
@@ -883,7 +883,12 @@ Execution rule: complete and verify each batch before starting the next batch.
|
|
|
883
883
|
| AC-1 | T-1 |
|
|
884
884
|
|
|
885
885
|
## Execution Posture
|
|
886
|
-
-
|
|
886
|
+
- execution.topology: auto | inline | single-builder | parallel-builders | strict-micro
|
|
887
|
+
- execution.strictness: fast | balanced | strict
|
|
888
|
+
- execution.maxBuilders: 5
|
|
889
|
+
- plan.sliceGranularity: feature-atomic | strict-micro
|
|
890
|
+
- plan.microTaskPolicy: advisory | strict
|
|
891
|
+
- Posture rationale: (why this is the cheapest safe topology)
|
|
887
892
|
- Stop conditions:
|
|
888
893
|
- Risk triggers:
|
|
889
894
|
- TDD checkpoint plan: RED commit/checkpoint -> GREEN commit/checkpoint -> REFACTOR commit/checkpoint (or deferred because: )
|
|
@@ -904,7 +909,7 @@ Execution rule: complete and verify each batch before starting the next batch.
|
|
|
904
909
|
| | | |
|
|
905
910
|
|
|
906
911
|
## Implementation Units
|
|
907
|
-
> Required structural form per implementation unit.
|
|
912
|
+
> Required structural form per implementation unit. Default is feature-atomic units with bite-sized 2-5 minute TDD steps inside each. Use strict micro-slices only for high-risk work or when explicitly configured.
|
|
908
913
|
|
|
909
914
|
### Implementation Unit U-1
|
|
910
915
|
- **id:** U-1
|
|
@@ -947,10 +952,10 @@ Execution rule: complete and verify each batch before starting the next batch.
|
|
|
947
952
|
- Required pre-merge proof:
|
|
948
953
|
|
|
949
954
|
## Execution Handoff
|
|
950
|
-
- **
|
|
951
|
-
- **Why this
|
|
952
|
-
- **Subagent recipe (if
|
|
953
|
-
- **Inline recipe (if
|
|
955
|
+
- **Topology chosen:** execution.topology = auto | inline | single-builder | parallel-builders | strict-micro
|
|
956
|
+
- **Why this topology:** (one line tying choice to unit count, path independence, risk, and maxBuilders)
|
|
957
|
+
- **Subagent recipe (if single/parallel builder):** \`<harness>\` -> \`<dispatch surface>\` -> \`<agent-definition path>\` (substitute neutral placeholders; full recipes in \`docs/harnesses.md\`)
|
|
958
|
+
- **Inline recipe (if inline):** TDD loop unit-by-unit with the same RED-before-GREEN, AC traceability, path containment, verification, and managed commit/worktree gates
|
|
954
959
|
|
|
955
960
|
## Plan Quality Scan
|
|
956
961
|
<!-- linter-meta -->
|
package/dist/delegation.d.ts
CHANGED
|
@@ -330,8 +330,9 @@ export declare class DispatchOverlapError extends Error {
|
|
|
330
330
|
/**
|
|
331
331
|
* Thrown when the count of active `slice-builder` spans reaches
|
|
332
332
|
* `MAX_PARALLEL_SLICE_BUILDERS` and a new scheduled row would push it past
|
|
333
|
-
* the cap. Cap can be
|
|
334
|
-
* flag or globally via
|
|
333
|
+
* the cap. Cap can be configured via `.cclaw/config.yaml::execution.maxBuilders`,
|
|
334
|
+
* overridden once via `--override-cap=N` on the hook flag, or globally via
|
|
335
|
+
* `CCLAW_MAX_PARALLEL_SLICE_BUILDERS=<N>` env.
|
|
335
336
|
*/
|
|
336
337
|
export declare class DispatchCapError extends Error {
|
|
337
338
|
readonly cap: number;
|
|
@@ -381,6 +382,45 @@ export declare class DispatchClaimedPathProtectedError extends Error {
|
|
|
381
382
|
* offending path so the operator can fix the dispatch in one pass.
|
|
382
383
|
*/
|
|
383
384
|
export declare function validateClaimedPathsNotProtected(stamped: DelegationEntry): void;
|
|
385
|
+
/**
|
|
386
|
+
* Thrown by `appendDelegation` (and the inline `delegation-record.mjs`
|
|
387
|
+
* helper) when an event with a non-null `phase` is recorded with
|
|
388
|
+
* `status="acknowledged"`. Phase-level granularity only makes sense on
|
|
389
|
+
* terminal outcomes (`completed` or `failed`); the dispatch-level ACK
|
|
390
|
+
* (no phase) is the controller saying "I see the dispatch surface back".
|
|
391
|
+
*
|
|
392
|
+
* Motivated by hox W-08/S-41: the slice-builder agent recorded all four
|
|
393
|
+
* phase events with `--status=acknowledged`, which the helper silently
|
|
394
|
+
* accepted but `slice-commit.mjs` only fires on `phase=doc status=completed`.
|
|
395
|
+
* `wave-status` then saw the slice as phantom-open even though the
|
|
396
|
+
* worker had finished. Recovery required raw backfill commands.
|
|
397
|
+
*
|
|
398
|
+
* 7.6.0 makes the constraint explicit: pair `--phase=<phase>` with
|
|
399
|
+
* `--status=completed` (or `--status=failed`) and use
|
|
400
|
+
* `--status=acknowledged` only for the dispatch-level ack (no phase).
|
|
401
|
+
*/
|
|
402
|
+
export declare class PhaseEventRequiresTerminalStatusError extends Error {
|
|
403
|
+
readonly phase: string;
|
|
404
|
+
readonly status: DelegationStatus;
|
|
405
|
+
readonly spanId: string;
|
|
406
|
+
readonly correctedCommandHint: string;
|
|
407
|
+
constructor(params: {
|
|
408
|
+
phase: string;
|
|
409
|
+
status: DelegationStatus;
|
|
410
|
+
spanId: string;
|
|
411
|
+
correctedCommandHint: string;
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Reject delegation rows where `phase` is set but `status` is not
|
|
416
|
+
* `completed` or `failed`. Acknowledged/launched/scheduled/waived/stale
|
|
417
|
+
* rows must NOT carry a phase — the phase-level lifecycle exists only
|
|
418
|
+
* to record terminal outcomes per phase (RED/GREEN/REFACTOR/DOC).
|
|
419
|
+
*
|
|
420
|
+
* Throws `PhaseEventRequiresTerminalStatusError`; the message includes
|
|
421
|
+
* an actionable corrected-command hint that the controller can paste.
|
|
422
|
+
*/
|
|
423
|
+
export declare function validatePhaseEventStatus(stamped: DelegationEntry): void;
|
|
384
424
|
/**
|
|
385
425
|
* Thrown by `appendDelegation` when a new `scheduled` span would open a
|
|
386
426
|
* second TDD cycle for a slice that already has at least one closed span
|
|
@@ -497,7 +537,7 @@ export declare function validateFileOverlap(stamped: DelegationEntry, activeEntr
|
|
|
497
537
|
};
|
|
498
538
|
/**
|
|
499
539
|
* Enforce the slice-builder fan-out cap. The new scheduled row pushes the
|
|
500
|
-
* active count from N to N+1; if that would exceed the cap (default 5,
|
|
540
|
+
* active count from N to N+1; if that would exceed the cap (default/config 5,
|
|
501
541
|
* env-overridable via `CCLAW_MAX_PARALLEL_SLICE_BUILDERS`), throw
|
|
502
542
|
* `DispatchCapError`.
|
|
503
543
|
*
|
package/dist/delegation.js
CHANGED
|
@@ -3,12 +3,13 @@ import path from "node:path";
|
|
|
3
3
|
import { execFile } from "node:child_process";
|
|
4
4
|
import { promisify } from "node:util";
|
|
5
5
|
import { RUNTIME_ROOT } from "./constants.js";
|
|
6
|
-
import { readConfig } from "./config.js";
|
|
6
|
+
import { readConfig, resolveMaxBuilders } from "./config.js";
|
|
7
7
|
import { exists, withDirectoryLock, writeFileSafe } from "./fs-utils.js";
|
|
8
8
|
import { HARNESS_ADAPTERS } from "./harness-adapters.js";
|
|
9
9
|
import { readFlowState } from "./runs.js";
|
|
10
10
|
import { mandatoryAgentsFor, stageSchema } from "./content/stage-schema.js";
|
|
11
11
|
import { compareCanonicalUnitIds, mergeParallelWaveDefinitions, parseImplementationUnitParallelFields, parseImplementationUnits, parseParallelExecutionPlanWaves, parseWavePlanDirectory } from "./internal/plan-split-waves.js";
|
|
12
|
+
import { compareSliceIds } from "./util/slice-id.js";
|
|
12
13
|
const execFileAsync = promisify(execFile);
|
|
13
14
|
const TERMINAL_DELEGATION_STATUSES = new Set(["completed", "failed", "waived", "stale"]);
|
|
14
15
|
export const DELEGATION_DISPATCH_SURFACES = [
|
|
@@ -615,8 +616,9 @@ export class DispatchOverlapError extends Error {
|
|
|
615
616
|
/**
|
|
616
617
|
* Thrown when the count of active `slice-builder` spans reaches
|
|
617
618
|
* `MAX_PARALLEL_SLICE_BUILDERS` and a new scheduled row would push it past
|
|
618
|
-
* the cap. Cap can be
|
|
619
|
-
* flag or globally via
|
|
619
|
+
* the cap. Cap can be configured via `.cclaw/config.yaml::execution.maxBuilders`,
|
|
620
|
+
* overridden once via `--override-cap=N` on the hook flag, or globally via
|
|
621
|
+
* `CCLAW_MAX_PARALLEL_SLICE_BUILDERS=<N>` env.
|
|
620
622
|
*/
|
|
621
623
|
export class DispatchCapError extends Error {
|
|
622
624
|
cap;
|
|
@@ -700,6 +702,69 @@ export function validateClaimedPathsNotProtected(stamped) {
|
|
|
700
702
|
spanId: stamped.spanId ?? "unknown"
|
|
701
703
|
});
|
|
702
704
|
}
|
|
705
|
+
/**
|
|
706
|
+
* Thrown by `appendDelegation` (and the inline `delegation-record.mjs`
|
|
707
|
+
* helper) when an event with a non-null `phase` is recorded with
|
|
708
|
+
* `status="acknowledged"`. Phase-level granularity only makes sense on
|
|
709
|
+
* terminal outcomes (`completed` or `failed`); the dispatch-level ACK
|
|
710
|
+
* (no phase) is the controller saying "I see the dispatch surface back".
|
|
711
|
+
*
|
|
712
|
+
* Motivated by hox W-08/S-41: the slice-builder agent recorded all four
|
|
713
|
+
* phase events with `--status=acknowledged`, which the helper silently
|
|
714
|
+
* accepted but `slice-commit.mjs` only fires on `phase=doc status=completed`.
|
|
715
|
+
* `wave-status` then saw the slice as phantom-open even though the
|
|
716
|
+
* worker had finished. Recovery required raw backfill commands.
|
|
717
|
+
*
|
|
718
|
+
* 7.6.0 makes the constraint explicit: pair `--phase=<phase>` with
|
|
719
|
+
* `--status=completed` (or `--status=failed`) and use
|
|
720
|
+
* `--status=acknowledged` only for the dispatch-level ack (no phase).
|
|
721
|
+
*/
|
|
722
|
+
export class PhaseEventRequiresTerminalStatusError extends Error {
|
|
723
|
+
phase;
|
|
724
|
+
status;
|
|
725
|
+
spanId;
|
|
726
|
+
correctedCommandHint;
|
|
727
|
+
constructor(params) {
|
|
728
|
+
super(`phase_event_requires_completed_or_failed_status — span ${params.spanId} recorded --phase=${params.phase} with --status=${params.status}; ` +
|
|
729
|
+
`phase-level events are only valid on terminal outcomes (--status=completed or --status=failed). ` +
|
|
730
|
+
`The dispatch-level ack (no --phase) can still use --status=acknowledged. ` +
|
|
731
|
+
`Corrected command: ${params.correctedCommandHint}`);
|
|
732
|
+
this.name = "PhaseEventRequiresTerminalStatusError";
|
|
733
|
+
this.phase = params.phase;
|
|
734
|
+
this.status = params.status;
|
|
735
|
+
this.spanId = params.spanId;
|
|
736
|
+
this.correctedCommandHint = params.correctedCommandHint;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* Reject delegation rows where `phase` is set but `status` is not
|
|
741
|
+
* `completed` or `failed`. Acknowledged/launched/scheduled/waived/stale
|
|
742
|
+
* rows must NOT carry a phase — the phase-level lifecycle exists only
|
|
743
|
+
* to record terminal outcomes per phase (RED/GREEN/REFACTOR/DOC).
|
|
744
|
+
*
|
|
745
|
+
* Throws `PhaseEventRequiresTerminalStatusError`; the message includes
|
|
746
|
+
* an actionable corrected-command hint that the controller can paste.
|
|
747
|
+
*/
|
|
748
|
+
export function validatePhaseEventStatus(stamped) {
|
|
749
|
+
if (typeof stamped.phase !== "string" || stamped.phase.length === 0)
|
|
750
|
+
return;
|
|
751
|
+
if (stamped.status === "completed" || stamped.status === "failed")
|
|
752
|
+
return;
|
|
753
|
+
const phase = stamped.phase;
|
|
754
|
+
const sliceFlag = typeof stamped.sliceId === "string" && stamped.sliceId.length > 0
|
|
755
|
+
? `--slice=${stamped.sliceId} `
|
|
756
|
+
: "";
|
|
757
|
+
const spanFlag = typeof stamped.spanId === "string" && stamped.spanId.length > 0
|
|
758
|
+
? `--span-id=${stamped.spanId} `
|
|
759
|
+
: "";
|
|
760
|
+
const correctedCommandHint = `node .cclaw/hooks/delegation-record.mjs --stage=${stamped.stage} --agent=${stamped.agent} --mode=${stamped.mode} --status=completed --phase=${phase} ${sliceFlag}${spanFlag}--evidence-ref="<phase outcome>"`;
|
|
761
|
+
throw new PhaseEventRequiresTerminalStatusError({
|
|
762
|
+
phase,
|
|
763
|
+
status: stamped.status,
|
|
764
|
+
spanId: stamped.spanId ?? "unknown",
|
|
765
|
+
correctedCommandHint
|
|
766
|
+
});
|
|
767
|
+
}
|
|
703
768
|
/**
|
|
704
769
|
* Thrown by `appendDelegation` when a new `scheduled` span would open a
|
|
705
770
|
* second TDD cycle for a slice that already has at least one closed span
|
|
@@ -845,7 +910,7 @@ export function readySliceUnitsFromMergedWaves(mergedWaves, planMarkdown, option
|
|
|
845
910
|
}
|
|
846
911
|
}
|
|
847
912
|
const out = [];
|
|
848
|
-
for (const sliceId of [...sliceSet].sort(
|
|
913
|
+
for (const sliceId of [...sliceSet].sort(compareSliceIds)) {
|
|
849
914
|
const member = mergedWaves.flatMap((w) => w.members).find((x) => x.sliceId === sliceId);
|
|
850
915
|
if (!member)
|
|
851
916
|
continue;
|
|
@@ -1047,7 +1112,7 @@ export function validateFileOverlap(stamped, activeEntries) {
|
|
|
1047
1112
|
}
|
|
1048
1113
|
/**
|
|
1049
1114
|
* Enforce the slice-builder fan-out cap. The new scheduled row pushes the
|
|
1050
|
-
* active count from N to N+1; if that would exceed the cap (default 5,
|
|
1115
|
+
* active count from N to N+1; if that would exceed the cap (default/config 5,
|
|
1051
1116
|
* env-overridable via `CCLAW_MAX_PARALLEL_SLICE_BUILDERS`), throw
|
|
1052
1117
|
* `DispatchCapError`.
|
|
1053
1118
|
*
|
|
@@ -1062,9 +1127,13 @@ export function validateFanOutCap(stamped, activeEntries, override) {
|
|
|
1062
1127
|
return;
|
|
1063
1128
|
if (stamped.status !== "scheduled")
|
|
1064
1129
|
return;
|
|
1065
|
-
const cap = (
|
|
1066
|
-
|
|
1067
|
-
|
|
1130
|
+
const cap = readMaxParallelOverrideFromEnv() ??
|
|
1131
|
+
(override !== null &&
|
|
1132
|
+
override !== undefined &&
|
|
1133
|
+
Number.isInteger(override) &&
|
|
1134
|
+
override >= 1
|
|
1135
|
+
? override
|
|
1136
|
+
: MAX_PARALLEL_SLICE_BUILDERS);
|
|
1068
1137
|
const sameLaneActive = activeEntries.filter((entry) => entry.stage === stamped.stage &&
|
|
1069
1138
|
entry.agent === stamped.agent &&
|
|
1070
1139
|
entry.spanId !== stamped.spanId);
|
|
@@ -1183,6 +1252,7 @@ export async function appendDelegation(projectRoot, entry) {
|
|
|
1183
1252
|
return;
|
|
1184
1253
|
}
|
|
1185
1254
|
validateMonotonicTimestamps(stamped, prior.entries);
|
|
1255
|
+
validatePhaseEventStatus(stamped);
|
|
1186
1256
|
if (stamped.status === "scheduled" &&
|
|
1187
1257
|
typeof stamped.sliceId === "string" &&
|
|
1188
1258
|
stamped.sliceId.length > 0 &&
|
|
@@ -1206,7 +1276,8 @@ export async function appendDelegation(projectRoot, entry) {
|
|
|
1206
1276
|
if (overlap.autoParallel && stamped.allowParallel !== true) {
|
|
1207
1277
|
stamped.allowParallel = true;
|
|
1208
1278
|
}
|
|
1209
|
-
|
|
1279
|
+
const config = await readConfig(projectRoot).catch(() => null);
|
|
1280
|
+
validateFanOutCap(stamped, activeForRun, resolveMaxBuilders(config));
|
|
1210
1281
|
if (stamped.allowParallel !== true) {
|
|
1211
1282
|
const existing = findActiveSpanForPair(stamped.stage, stamped.agent, activeRunId, prior);
|
|
1212
1283
|
if (existing && existing.spanId && existing.spanId !== stamped.spanId) {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ExecutionStrictnessProfile, ExecutionTopology } from "./types.js";
|
|
2
|
+
export interface ExecutionTopologyShape {
|
|
3
|
+
/** Ready implementation units/slices the controller could execute now. */
|
|
4
|
+
unitCount: number;
|
|
5
|
+
/** Ready units with no declared dependency/path conflict against each other. */
|
|
6
|
+
independentUnitCount?: number;
|
|
7
|
+
/** Ready units large enough to justify isolated builder overhead. */
|
|
8
|
+
substantialUnitCount?: number;
|
|
9
|
+
/** True when same-wave path ownership overlaps or is unknown-dangerous. */
|
|
10
|
+
hasPathConflicts?: boolean;
|
|
11
|
+
/** True for migrations, public contracts, security, data loss, or broad API changes. */
|
|
12
|
+
highRisk?: boolean;
|
|
13
|
+
/** True when a plan or controller explicitly requests micro-slice discipline. */
|
|
14
|
+
requiresStrictMicro?: boolean;
|
|
15
|
+
/** True when the controller can safely execute the unit inline in the current harness. */
|
|
16
|
+
inlineSafe?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface ExecutionTopologyDecision {
|
|
19
|
+
topology: Exclude<ExecutionTopology, "auto">;
|
|
20
|
+
maxBuilders: number;
|
|
21
|
+
reason: string;
|
|
22
|
+
}
|
|
23
|
+
export interface RouteExecutionTopologyOptions {
|
|
24
|
+
configuredTopology?: ExecutionTopology;
|
|
25
|
+
strictness?: ExecutionStrictnessProfile;
|
|
26
|
+
maxBuilders?: number;
|
|
27
|
+
shape: ExecutionTopologyShape;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Choose the cheapest safe execution topology for a ready TDD surface.
|
|
31
|
+
*
|
|
32
|
+
* Safety invariants are intentionally conservative: explicit strict-micro wins,
|
|
33
|
+
* path conflicts prevent fan-out, and parallel builders require multiple
|
|
34
|
+
* independent substantial units plus a builder cap above one.
|
|
35
|
+
*/
|
|
36
|
+
export declare function routeExecutionTopology(options: RouteExecutionTopologyOptions): ExecutionTopologyDecision;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const DEFAULT_MAX_BUILDERS = 5;
|
|
2
|
+
function normalizeMaxBuilders(value) {
|
|
3
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 1
|
|
4
|
+
? value
|
|
5
|
+
: DEFAULT_MAX_BUILDERS;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Choose the cheapest safe execution topology for a ready TDD surface.
|
|
9
|
+
*
|
|
10
|
+
* Safety invariants are intentionally conservative: explicit strict-micro wins,
|
|
11
|
+
* path conflicts prevent fan-out, and parallel builders require multiple
|
|
12
|
+
* independent substantial units plus a builder cap above one.
|
|
13
|
+
*/
|
|
14
|
+
export function routeExecutionTopology(options) {
|
|
15
|
+
const configured = options.configuredTopology ?? "auto";
|
|
16
|
+
const strictness = options.strictness ?? "balanced";
|
|
17
|
+
const maxBuilders = normalizeMaxBuilders(options.maxBuilders);
|
|
18
|
+
const shape = options.shape;
|
|
19
|
+
if (configured !== "auto") {
|
|
20
|
+
return {
|
|
21
|
+
topology: configured,
|
|
22
|
+
maxBuilders,
|
|
23
|
+
reason: `configured execution.topology=${configured}`
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
if (shape.requiresStrictMicro || strictness === "strict") {
|
|
27
|
+
return {
|
|
28
|
+
topology: "strict-micro",
|
|
29
|
+
maxBuilders,
|
|
30
|
+
reason: shape.requiresStrictMicro
|
|
31
|
+
? "plan requested strict micro-slice execution"
|
|
32
|
+
: "strict execution profile selected"
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const unitCount = Math.max(0, shape.unitCount);
|
|
36
|
+
if (unitCount === 0) {
|
|
37
|
+
return {
|
|
38
|
+
topology: "inline",
|
|
39
|
+
maxBuilders,
|
|
40
|
+
reason: "no ready units; controller can reconcile inline"
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
if (shape.hasPathConflicts) {
|
|
44
|
+
return {
|
|
45
|
+
topology: "single-builder",
|
|
46
|
+
maxBuilders,
|
|
47
|
+
reason: "path conflicts require serialized execution"
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const independent = shape.independentUnitCount ?? unitCount;
|
|
51
|
+
const substantial = shape.substantialUnitCount ?? unitCount;
|
|
52
|
+
if (maxBuilders > 1 && independent >= 2 && substantial >= 2) {
|
|
53
|
+
return {
|
|
54
|
+
topology: "parallel-builders",
|
|
55
|
+
maxBuilders,
|
|
56
|
+
reason: "multiple independent substantial units are ready"
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
if (unitCount === 1 && shape.inlineSafe && !shape.highRisk) {
|
|
60
|
+
return {
|
|
61
|
+
topology: "inline",
|
|
62
|
+
maxBuilders,
|
|
63
|
+
reason: "single low-risk inline-safe unit"
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
topology: "single-builder",
|
|
68
|
+
maxBuilders,
|
|
69
|
+
reason: unitCount === 1
|
|
70
|
+
? "single ready unit keeps builder evidence isolated"
|
|
71
|
+
: "ready units are not safely parallelizable"
|
|
72
|
+
};
|
|
73
|
+
}
|
package/dist/gate-evidence.js
CHANGED
|
@@ -11,6 +11,7 @@ import { computeEarlyLoopStatus, isEarlyLoopStage, normalizeEarlyLoopMaxIteratio
|
|
|
11
11
|
import { detectPublicApiChanges } from "./internal/detect-public-api-changes.js";
|
|
12
12
|
import { detectSupplyChainChanges } from "./internal/detect-supply-chain-changes.js";
|
|
13
13
|
import { readFlowState, writeFlowState } from "./runs.js";
|
|
14
|
+
import { loadStackAdapter } from "./stack-detection.js";
|
|
14
15
|
import { validateTddVerificationEvidence } from "./tdd-verification-evidence.js";
|
|
15
16
|
async function currentStageArtifactExists(projectRoot, stage, track) {
|
|
16
17
|
const resolved = await resolveArtifactPath(stage, {
|
|
@@ -88,20 +89,17 @@ async function discoverRealTestCommands(projectRoot) {
|
|
|
88
89
|
commands.push(name === "test" ? "bun test" : `bun run ${name}`);
|
|
89
90
|
}
|
|
90
91
|
}
|
|
91
|
-
|
|
92
|
-
|
|
92
|
+
// 7.6.0 — pull additional commands from the stack-adapter's
|
|
93
|
+
// testCommandHints rather than hardcoding pytest/go test/cargo
|
|
94
|
+
// test/mvn/gradle here. Adapters that don't apply to the project
|
|
95
|
+
// contribute no commands; pytest.ini support is kept as an
|
|
96
|
+
// explicit fallback because pyproject.toml-less projects exist.
|
|
97
|
+
const stackAdapter = await loadStackAdapter(projectRoot);
|
|
98
|
+
for (const hint of stackAdapter.testCommandHints) {
|
|
99
|
+
commands.push(hint);
|
|
100
|
+
}
|
|
93
101
|
if (await exists(path.join(projectRoot, "pytest.ini")))
|
|
94
102
|
commands.push("pytest");
|
|
95
|
-
if (await exists(path.join(projectRoot, "go.mod")))
|
|
96
|
-
commands.push("go test ./...");
|
|
97
|
-
if (await exists(path.join(projectRoot, "Cargo.toml")))
|
|
98
|
-
commands.push("cargo test");
|
|
99
|
-
if (await exists(path.join(projectRoot, "pom.xml")))
|
|
100
|
-
commands.push("mvn test");
|
|
101
|
-
if (await exists(path.join(projectRoot, "build.gradle")) ||
|
|
102
|
-
await exists(path.join(projectRoot, "build.gradle.kts"))) {
|
|
103
|
-
commands.push("gradle test", "./gradlew test");
|
|
104
|
-
}
|
|
105
103
|
return unique(commands);
|
|
106
104
|
}
|
|
107
105
|
async function verifyDiscoveredCommandEvidence(projectRoot, stage, gateId, flowState) {
|
|
@@ -4,7 +4,7 @@ import { RUNTIME_ROOT } from "../../constants.js";
|
|
|
4
4
|
import { createInitialFlowState } from "../../flow-state.js";
|
|
5
5
|
import { readFlowState, writeFlowState } from "../../runs.js";
|
|
6
6
|
import { listExistingFiles, listFilesUnder, pathExists } from "./helpers.js";
|
|
7
|
-
import { STACK_DISCOVERY_DIR_MARKERS, STACK_DISCOVERY_MARKERS } from "../../stack-detection.js";
|
|
7
|
+
import { STACK_DISCOVERY_DIR_MARKERS, STACK_DISCOVERY_MARKERS, loadStackAdapter } from "../../stack-detection.js";
|
|
8
8
|
import { TRACK_STAGES } from "../../types.js";
|
|
9
9
|
import { buildValidationReport } from "./advance.js";
|
|
10
10
|
import { carriedCompletedStageCatalog, completedStageClosureEvidenceIssues, firstIncompleteStageForTrack } from "./verify.js";
|
|
@@ -58,11 +58,20 @@ export async function collectRepoSignals(projectRoot) {
|
|
|
58
58
|
// ignore
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
|
-
|
|
61
|
+
// 7.6.0 — manifest detection now routes through the stack-adapter
|
|
62
|
+
// contract instead of hardcoding `package.json` / `pyproject.toml` /
|
|
63
|
+
// `Cargo.toml`. Adapters that declare manifestGlobs probe their
|
|
64
|
+
// declared paths; the unknown adapter is a no-op.
|
|
65
|
+
const stackAdapter = await loadStackAdapter(projectRoot);
|
|
66
|
+
for (const manifestGlob of stackAdapter.manifestGlobs) {
|
|
67
|
+
if (manifestGlob.includes("*"))
|
|
68
|
+
continue;
|
|
62
69
|
try {
|
|
63
|
-
const st = await fs.stat(path.join(projectRoot,
|
|
64
|
-
if (st.isFile())
|
|
70
|
+
const st = await fs.stat(path.join(projectRoot, manifestGlob));
|
|
71
|
+
if (st.isFile()) {
|
|
65
72
|
hasPackageManifest = true;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
66
75
|
}
|
|
67
76
|
catch {
|
|
68
77
|
// ignore
|