archgen-skill 0.0.4 → 0.0.5

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.
@@ -96,6 +96,63 @@ shrinking LARGE hallucinates.
96
96
  per-task edge-case matrices + per-module diagrams + MEDIUM-grade plan
97
97
  sections. Every diagram ≤15 nodes.
98
98
 
99
+ ## Structural correctness — verifier-enforced authoring rules
100
+
101
+ - **Waves derive from `depends_on`.** Declared wave / `parallel_group` labels
102
+ MUST match the dependency-derived topological order — never hand-label a
103
+ task into a wave that contradicts its `depends_on`. A dependent task may not
104
+ sit in an earlier (or parallel) wave than its prerequisite.
105
+ `plan-graph.mjs --mermaid` is the ground truth for wave order.
106
+ - **artifacts ⊆ file_ownership.** Every path a task lists in `artifacts` must
107
+ fall inside that task's `file_ownership` globs — workers can write ONLY
108
+ owned paths. Listing a doc the task doesn't own is a verifier-blocking
109
+ defect.
110
+ - **Verification commands**: repo-root-relative (no `../`, no `--plan .`),
111
+ fully specific — exact commands, versions, sample sizes, thresholds — the
112
+ same form a worker would actually run. Generic "run the tests" or
113
+ wrong-root-resolved paths fail.
114
+ - **YAML subset**: emit only the parser's supported subset — block mappings
115
+ and block sequences only. Avoid flow mappings (`{...}`), block scalars
116
+ (`|` / `>`), duplicate keys, and comma-heavy inline values, which the
117
+ strict parser rejects (these caused format-repair loops).
118
+ - **tasks.yaml edits**: make small targeted edits or one clean full
119
+ regeneration — never large multi-region patches (they corrupted tasks.yaml
120
+ in the field).
121
+
122
+ ## Agent workspace & artifact location — the two-tier rule
123
+
124
+ Location follows LIFETIME + AUDIENCE: the plan (and its rationale) travels as
125
+ ONE self-contained, reviewable, git-versioned unit; the product's deliverables
126
+ travel with the product.
127
+
128
+ INSIDE `.archgen/<slug>/` — the plan + everything needed to execute/review it:
129
+ - tasks.yaml, plans/, architecture.yaml, answers.yaml, codebase-map.md.
130
+ - `docs/prd.md` — a PLANNING artifact (defines the FR-ids tasks cite), not a
131
+ shipped doc — stays inside so doc-index can validate FR references.
132
+ - `decisions/NNNN-title.md` — the plan's formal ADRs (its rationale). Inside
133
+ keeps the feature folder self-contained and ADR↔task↔plan references valid.
134
+ - `.agent/` — ALL agent/sub-agent working state. The leading dot keeps it out
135
+ of doc-index scans (process state, not a validated artifact):
136
+ - `.agent/decisions/NNNN-<slug>.md` — EXECUTION-time decisions a sub-agent
137
+ takes while working (distinct from the plan's formal ADRs in `decisions/`).
138
+ - `.agent/learnings.md` — conventions/patterns discovered (append-only).
139
+ - `.agent/notes/<task-id-or-topic>.md` — worker/survey/investigator notes.
140
+ - `.agent/reports/<verifier|reviewer|investigate>-<round>.md` — persisted
141
+ verifier/reviewer/investigation findings.
142
+
143
+ OUTSIDE `.archgen/` — project DELIVERABLES an approved task produces, written
144
+ to the task's `file_ownership` paths at the repo root (never into `.archgen/`):
145
+ - source code (`src/...`), and
146
+ - shipped documentation: README, API reference, deployment/runbooks, user
147
+ guides — the docs the running software ships to users/operators.
148
+
149
+ Enterprise refinement — durable ADRs: a decision that outlives this feature
150
+ (project-wide, e.g. "Postgres over MongoDB") SHOULD also be promoted to the
151
+ project's canonical ADR register (`docs/adr/` or the team's equivalent) so
152
+ future features discover it. `.archgen/<slug>/decisions/` is the working
153
+ record; the register is the durable project-wide copy. Never delete or
154
+ re-number an accepted ADR — supersede it.
155
+
99
156
  ## architecture.yaml (LARGE scope)
100
157
 
101
158
  The four contract blocks below (`naming_conventions`, `data_contracts`,
@@ -1,20 +1,53 @@
1
1
  # Interview playbook — requirement elicitation
2
2
 
3
3
  Goal: complete, conflict-free requirements in the FEWEST round-trips.
4
- Batch 3–5 questions per message. Never ask what you can infer.
4
+ Batch 3–5 questions per message. Never ask what you can infer — ask only
5
+ decision-forks, state everything else as a veto-able decision (§ How to ask).
6
+
7
+ ## How to ask
8
+
9
+ - **Tool-first, options-fallback.** Use your harness's ask-question /
10
+ interactive multiple-choice tool when one exists; no such tool → ask inline
11
+ with labeled options. Same content either way.
12
+ - **Recommendation-first.** Every option set leads with YOUR best judgment
13
+ tagged `(recommended)`, then alternatives. The user accepts the
14
+ recommendation by default and may override — never present bare options
15
+ without a recommendation.
16
+ - **Ask forks, state decisions.** Ask ONLY the few decision-forks that
17
+ materially branch the plan. Everything derivative: DECIDE autonomously and
18
+ announce — "I'll use X because Y — say so if you'd rather Z" — so the user
19
+ can veto without being interrogated.
20
+ - **Volume scales with scope.** Round caps (§ Step 1) are MAXIMA, not targets:
21
+ SMALL/simple prompt → at most 1–2 core questions, decide the rest; MEDIUM →
22
+ the targeted set; LARGE — or an experienced engineer signaling they want
23
+ control → the fuller discovery set.
24
+
25
+ Worked example (recommendation-first, inline fallback form):
26
+
27
+ > Rollout strategy?
28
+ > 1. Feature flag + staged rollout **(recommended)** — existing users unaffected
29
+ > 2. Immediate cutover — simpler, riskier
30
+ > 3. Versioned API alongside — only if existing clients break
31
+ > Reply 1–3, or just "ok" to accept the recommendation.
5
32
 
6
33
  ## Round 0 — calibration exchange (skippable)
7
34
 
8
- Two or three rapid questions, asked once at the very start:
35
+ Two or three rapid items, asked once at the very start — each INFERRED first
36
+ (§ How to ask), asked only when genuinely ambiguous:
9
37
 
10
- 1. Rough scale: prototype, MVP, or production-bound?
38
+ 1. Rough scale: prototype, MVP, or production-bound? INFER from the opening
39
+ message + repo context; state the inference + one line of reasoning and
40
+ let the user confirm or correct. Ask open-ended ONLY when genuinely
41
+ ambiguous.
11
42
  2. Team size + convention constraints? (solo? existing lint/CI/style guides?)
12
43
  3. Deadline pressure: none, moderate, crunch?
13
44
 
14
45
  Purpose is ONLY to pick question-set depth and right-size later verification
15
46
  rigor — never to collect requirements. SKIP the whole exchange when the user's
16
- opening message already answers these items (e.g. "quick prototype by Friday").
17
- Record whatever was captured under `calibration:` in answers.yaml.
47
+ opening message already answers these items (e.g. "quick prototype by
48
+ Friday"); SMALL/simple prompts skip calibration entirely and go straight to
49
+ their 1–2 core forks (§ How to ask). Record whatever was captured under
50
+ `calibration:` in answers.yaml.
18
51
 
19
52
  ## Step 1 — classify intent (decide during round 1)
20
53
 
@@ -31,6 +64,17 @@ the user in one sentence, and record it as `intent_class` in answers.yaml:
31
64
  Never interrogate a BROWNFIELD-FEATURE with PRD-grade questions — token waste,
32
65
  user annoyance. Round caps INCLUDE round 0 when it runs.
33
66
 
67
+ Default `scope_class` from the intent class (user's choice wins — scope_class
68
+ is agreed in GENERATE step 1 and recorded in answers.yaml):
69
+ GREENFIELD-SYSTEM→LARGE, GREENFIELD-MODULE→MEDIUM,
70
+ BROWNFIELD-FEATURE→MEDIUM/SMALL, BROWNFIELD-CHANGE→SMALL.
71
+
72
+ Scale is INFERRED, not asked: derive scope_class from the opening message +
73
+ repo context + the class above, state it with one line of reasoning, and let
74
+ the user confirm or correct. Ask about scale ONLY when genuinely
75
+ ambiguous/confused. Small/simple prompts: skip heavy calibration, ask only
76
+ the 1–2 core forks (§ How to ask), decide the rest.
77
+
34
78
  ## GREENFIELD-SYSTEM — professional discovery (≤6 rounds)
35
79
 
36
80
  Creating a system from scratch demands PRD-grade inputs. Each round below
@@ -158,7 +202,8 @@ resolve before proceeding. [wait for user]
158
202
  ## Token discipline
159
203
 
160
204
  - Batch 3–5 questions per message; never ping-pong one-per-message.
161
- - Hard round caps: GREENFIELD-SYSTEM ≤6 rounds, every other class ≤3
205
+ - Hard round caps — MAXIMA, not targets (small prompts ask far fewer;
206
+ § How to ask): GREENFIELD-SYSTEM ≤6 rounds, every other class ≤3
162
207
  (round 0 counts when it runs).
163
208
  - Skip any question already answered by the opening message, calibration, or
164
209
  survey artifacts.
@@ -24,29 +24,127 @@ Accept the map only if every section is present and non-empty.
24
24
 
25
25
  ## §verifier — plan review brief
26
26
 
27
- Dispatch AFTER artifacts exist and validate.mjs passes. The verifier:
27
+ Dispatch AFTER artifacts exist and validate.mjs passes.
28
+
29
+ ### Agent tier — EVERY verifier dispatch
30
+
31
+ Verification is judgment-heavy: a weak verifier misses real defects AND raises
32
+ false positives — costing MORE rounds, not fewer. Dispatch on the strongest
33
+ reasoning agent the harness offers (best agent present), or a mid/high-tier
34
+ sub-agent scaled to plan complexity:
35
+
36
+ - SMALL/MEDIUM plan → one strong (mid/high-tier) agent.
37
+ - LARGE plan → the stronger/highest tier available.
38
+ - NEVER an explore / research / quick / small / cheap / fast sub-agent,
39
+ whatever the harness calls its low-end tiers.
40
+
41
+ ### Verification pass — FULL-DEPTH, every run
42
+
43
+ Each verifier run executes ALL four steps:
28
44
 
29
45
  1. Runs `node <skill>/scripts/verify-plan.mjs .archgen/<slug>/tasks.yaml --plan .archgen/<slug>/plans`
30
46
  — deterministic floor: cycles, dangling refs, same-wave ownership overlap,
31
47
  missing acceptance criteria, plan↔task coverage.
32
- 2. Reads plans + tasks.yaml and judges what scripts cannot:
48
+ 2. When markdown artifacts exist under `.archgen/<slug>/`, runs
49
+ `node <skill>/scripts/doc-index.mjs .archgen/<slug> --validate` — must be
50
+ clean (zero broken references, exit 0) alongside verify-plan APPROVE;
51
+ broken references block APPROVAL.
52
+ 3. Reads plans + tasks.yaml and judges what scripts cannot:
33
53
  - Are wave boundaries sensible (no artificial serialization)?
34
54
  - Do acceptance criteria objectively verify (command or observable)?
35
55
  - Edge cases covered per SKILL.md right-sizing class?
36
56
  - Code-standards compliance plausible for the planned structure?
37
- 3. Returns EXACTLY one line verdict: `APPROVE` or `ISSUES:` + numbered list.
38
-
39
- Orchestrator fixes issues (edits artifacts) and re-dispatches. Loop until
40
- APPROVE — never present to the user before verifier approval.
57
+ - CONTRACT-COMPLETENESS sweep — round 1 must confirm OR flag-as-missing
58
+ every APPLICABLE dimension (later discovery = the round-1 sweep failed,
59
+ not a gap). Apply a dimension only where the system actually has that
60
+ concern — skip what genuinely doesn't apply, but NEVER skip a dimension
61
+ the system touches:
62
+ - **Functional coverage**: every stated requirement/feature has tasks +
63
+ objectively verifiable acceptance.
64
+ - **Security & data protection** (if it authenticates, authorizes,
65
+ encrypts, or handles sensitive data): authn/authz flows, session &
66
+ credential lifecycle, replay/CSRF protection, encryption & privacy
67
+ boundaries, input validation — each fully specified, not named-and-
68
+ skipped.
69
+ - **Data integrity**: schemas, relationships, validation, and migration /
70
+ back-compat for any persisted state.
71
+ - **State, lifecycle & recovery** (if any operation is stateful,
72
+ long-running, or multi-step): checkpointing/resume, idempotency,
73
+ cleanup, expiry, compensation/rollback semantics.
74
+ - **Resource limits & abuse** (if it exposes shared or anonymous
75
+ resources): quotas, rate limits, retention/deletion, anonymous-abuse
76
+ controls.
77
+ - **Operational entrypoints**: every stack-required build/deploy/test/
78
+ bootstrap entrypoint is OWNED by some task (no orphan entrypoint).
79
+ - **Metric traceability**: every PRD success metric maps to a task
80
+ acceptance criterion with a reproducible command / sample size / data
81
+ source.
82
+ - **Consistency**: actively check cross-contract claims for internal
83
+ contradiction (a claim in one artifact that another artifact violates).
84
+ These failure classes are the TOP cause of multi-round loops — round 1
85
+ MUST sweep the applicable dimensions exhaustively.
86
+ 4. Returns EXACTLY one line verdict: `APPROVE` or `ISSUES:` + numbered list.
87
+
88
+ ### Efficiency — fewer rounds + zero duplicate work, NEVER shallower checks
89
+
90
+ Every run stays FULL-DEPTH: both scripts run to completion and the full
91
+ judgment checklist applies each time. Efficiency comes ONLY from:
92
+
93
+ - capping automatic rounds at 2 (gate protocol below);
94
+ - batching fixes per file (fix discipline below);
95
+ - never re-fixing an already-resolved finding.
96
+
97
+ "Efficient" NEVER means skipping or lightening verify-plan.mjs, doc-index.mjs,
98
+ or any judgment-checklist item.
99
+
100
+ ### Fix discipline — batch per file, dedupe repeats
101
+
102
+ On `ISSUES:` the orchestrator fixes ALL findings before re-dispatching:
103
+
104
+ - Group findings BY FILE; apply each file's fixes in ONE edit pass — open
105
+ each file once per round, not once per finding.
106
+ - Round N reports the SAME finding as round N-1 (same file + same issue) →
107
+ the prior fix did not land: re-read the file and fix it properly — never
108
+ re-apply the identical edit.
109
+
110
+ ### Gate protocol — 2 automatic rounds, then ask the user
111
+
112
+ The initial VERIFIER GATE flow (GENERATE step 5). Rounds 1–2 run
113
+ automatically; fix ALL findings between rounds (fix discipline above):
114
+
115
+ 1. Round 1: dispatch verifier. `APPROVE` → gate passed → §plan-review.
116
+ `ISSUES:` → fix ALL findings → round 2.
117
+ 2. Round 2: dispatch verifier.
118
+ - Clean `APPROVE` → ask the user once more, recommending NO, proceed.
119
+ - `ISSUES:` → fix ALL findings → ask the user once more, recommending
120
+ YES, one more pass.
121
+ Ask recommendation-first per interview.md § How to ask: "Run the verifier
122
+ once more to make sure everything passes?"
123
+ 3. On YES → run another round; if it returns `ISSUES:`, fix all and ask again
124
+ — every round beyond 2 is user-approved. On NO → proceed to §plan-review.
125
+ 4. On NO with findings still outstanding and no verifier `APPROVE` on record:
126
+ surface every outstanding finding verbatim and require an EXPLICIT user
127
+ waiver — never proceed silently. Hard rule 2 stands: the plan MUST NOT
128
+ reach the USER GATE without at least one verifier `APPROVE` (or that
129
+ explicit waiver on the record). An earlier `APPROVE` on record → proceed
130
+ to §plan-review, noting the latest unverified fixes.
41
131
 
42
132
  ## §plan-review — post-plan review stage
43
133
 
44
- Run AFTER verify-plan returns APPROVE and BEFORE the user gate. Dispatch one
45
- or more REVIEWER sub-agents; count scales with the right-sizing class:
134
+ Run AFTER verify-plan returns APPROVE and BEFORE the user gate. Dispatch
135
+ REVIEWER sub-agents sized by the right-sizing class — always ONE holistic
136
+ review of the WHOLE plan, never per-concern splits:
46
137
 
47
138
  - SMALL: main-agent self-review acceptable if no reviewer mechanism exists.
48
139
  - MEDIUM: 1 reviewer.
49
- - LARGE: 2–3 reviewers split by concern (correctness / ordering / docs).
140
+ - LARGE: 1 holistic reviewer on the strongest reasoning tier. A 2nd reviewer
141
+ ONLY if the plan is genuinely too large for one reviewer's context — split
142
+ by ARTIFACT/VOLUME then (e.g. plans vs docs), never by concern.
143
+
144
+ One holistic reviewer analyzes ALL issues together: cross-issue reasoning
145
+ catches contradictions a narrow per-concern slice misses, and one reviewer
146
+ costs far fewer tokens than several. NEVER dispatch one sub-agent per
147
+ individual issue/concern.
50
148
 
51
149
  ```
52
150
  TASK: Review the plan in .archgen/<slug>/ READ-ONLY.
@@ -61,9 +159,29 @@ You NEVER edit files. Report only.
61
159
  ```
62
160
 
63
161
  Reviewers are read-only and report-only — they never edit artifacts or code.
64
- The orchestrator fixes the artifacts itself, re-runs validate.mjs + re-dispatches
65
- the verifier gate after every change, and loops until zero findings remain or
66
- findings are explicitly waived by the user.
162
+ The orchestrator fixes the artifacts itself, re-runs validate.mjs +
163
+ re-dispatches the verifier gate after every change.
164
+
165
+ ### Review loop — bounded passes, then ask
166
+
167
+ The fix→re-verify cycle stays, but reviewer passes are countable and bounded:
168
+
169
+ 1. Pass 1: holistic review. Zero findings → stage done.
170
+ 2. Findings → fix ALL (batched per file), re-run validate.mjs + the verifier
171
+ gate → pass 2.
172
+ 3. Pass 2 still yields findings → do NOT spawn more reviewers. Consolidate
173
+ the outstanding findings and ask the user once, recommendation-first per
174
+ interview.md § How to ask: "Run one more targeted review pass on the N
175
+ open findings? 1. One more pass (recommended) 2. Waive these findings and
176
+ proceed."
177
+ 4. On YES → run EXACTLY ONE more pass, then consolidate and STOP. Zero
178
+ findings → stage done; findings remain → surface them and require an
179
+ explicit user waiver before the user gate. On NO → record the explicit
180
+ waiver, proceed.
181
+
182
+ A "more review" approval authorizes EXACTLY ONE additional pass — never a
183
+ fresh loop, never more reviewers. The stage ends ONLY at zero findings or an
184
+ explicit user waiver on record.
67
185
 
68
186
  ## §investigate — root-cause protocol
69
187
 
@@ -150,10 +268,15 @@ Every worker receives ALL of:
150
268
  `node <skill>/scripts/set-status.mjs .archgen/<slug>/tasks.yaml <id> done|failed`;
151
269
  - commit guidance: commit completed work scoped to the owned files;
152
270
  - BEFORE writing code run
153
- `node <skill>/scripts/plan-graph.mjs --node <task-id> --tasks <tasks.yaml>`
271
+ `node <skill>/scripts/plan-graph.mjs <slug-dir-or-tasks.yaml> --node <task-id>`
154
272
  and honor every connected dependency shown;
155
273
  - all file paths come from tasks.yaml/architecture.yaml VERBATIM — never
156
274
  reconstruct paths from memory.
275
+ - working state goes under `.archgen/<slug>/.agent/` — execution decisions to
276
+ `.agent/decisions/`, notes to `.agent/notes/<id>.md` (see
277
+ references/artifact-templates.md § Agent workspace & artifact location).
278
+ NEVER create ad-hoc note/context/decision folders at the repo root or
279
+ anywhere outside `.archgen/`.
157
280
 
158
281
  Workers do NOT talk to each other; coordination happens only through
159
282
  tasks.yaml state.
@@ -81,3 +81,58 @@ Claude Code project-scope servers.
81
81
  2. Prefer official vendor domains over blog posts; note the date of sources.
82
82
  3. Present findings + proposed config diff to the user. [approval gate]
83
83
  4. Apply, verify, and offer to append findings to this table upstream.
84
+
85
+ ## Codegraph indexing scope
86
+
87
+ Current mainstream codegraph indexers extract SOURCE symbols only
88
+ (functions/classes/imports) — they do not index markdown headings, and YAML
89
+ support ranges from absent to file-level config tracking; none can graph
90
+ tasks.yaml dependencies (audited 8 codegraph-family tools Aug 2026; local
91
+ binary v1.0.1 empirically confirms). Generic code indexers verifiably do not
92
+ extract markdown headings or YAML task semantics, so archgen ships its own
93
+ deterministic graph tools:
94
+
95
+ - Source navigation → codegraph MCP when configured (offered at Step 0).
96
+ - YAML plan graphs → exclusively `scripts/plan-graph.mjs`.
97
+ - Markdown artifact navigation → `scripts/doc-index.mjs`.
98
+
99
+ If the harness exposes a markdown-heading indexer (e.g. a
100
+ `codegraph_index_markdown`-style tool), prefer it for general repo docs but
101
+ still use `doc-index.mjs` inside `.archgen/` — it understands TASK/FR
102
+ reference semantics. Brownfield surveys prefer codegraph once present.
103
+
104
+ ### Codegraph install & index (concrete — researched Aug 2026, v1.0.1)
105
+
106
+ CodeGraph = `@colbymchenry/codegraph` (npm), CLI `codegraph`, MIT, 100% local.
107
+ It persists a SQLite symbol graph in `.codegraph/` and serves it over MCP. It
108
+ indexes SOURCE symbols only (scope above) — pair with `doc-index.mjs` /
109
+ `plan-graph.mjs` for markdown/YAML. Requires Node 22 or 24 (native SQLite
110
+ bindings; other versions fall back to a 5–10x-slower WASM backend).
111
+
112
+ Install the CLI (one-time, global):
113
+ - `npm i -g @colbymchenry/codegraph` (any OS with Node), or
114
+ - `curl -fsSL https://raw.githubusercontent.com/colbymchenry/codegraph/main/install.sh | sh` (macOS/Linux), or
115
+ - zero-install: `npx @colbymchenry/codegraph`.
116
+
117
+ Wire the harness (writes MCP config + instructions — INSTALL-MCP approval gate):
118
+ - `codegraph install --yes` (auto-detect agents, global)
119
+ - `codegraph install --target=<agent> --yes` (explicit harness)
120
+ - `codegraph install --target=auto --location=local --yes` (project-local)
121
+ - `codegraph install --print-config <agent>` (preview snippet, no writes)
122
+ - Manual config shape: assets/mcp-template.json (stdio `codegraph serve --mcp`
123
+ or npx `-y @colbymchenry/codegraph mcp`).
124
+
125
+ Index the project (per project; creates `.codegraph/` + builds the graph):
126
+ - `codegraph init` (add `--yes` to skip prompts); one-shot bootstrap:
127
+ `codegraph install --yes --init`. Auto-syncs on file change thereafter.
128
+ - `codegraph status` to verify; `codegraph uninit` removes the index;
129
+ `codegraph uninstall` de-wires the agents; `codegraph upgrade` updates.
130
+
131
+ `.codegraph/` is deterministic and small — committing it gives teammates the
132
+ index on first checkout (team recommendation); git-ignore it for solo repos.
133
+ Never point two OSes (Windows + WSL) at one `.codegraph/`.
134
+
135
+ Agent flow: detect `.codegraph/` at repo root. Present → use `codegraph_explore`
136
+ for source navigation. Absent → offer once (INSTALL-MCP approval pattern,
137
+ recommendation-first), run the commands above on approval; on decline use
138
+ Glob+Grep.
@@ -2,16 +2,26 @@
2
2
  // WHY a lib: validate.mjs, next-tasks.mjs and verify-plan.mjs must agree on
3
3
  // cycle detection and wave semantics; three copies would drift (Metis finding).
4
4
 
5
+ /** True iff `t` is a mapping object (not null, not an array, not a scalar). */
6
+ function isTaskMapping(t) {
7
+ return t !== null && typeof t === 'object' && !Array.isArray(t);
8
+ }
9
+
5
10
  /** Build adjacency (prerequisite → dependents) + reverse maps from tasks.
6
11
  * Normative semantics: depends_on lists PREREQUISITE ids; a task is ready iff
7
- * every prerequisite is done; wave 1 = empty depends_on. */
12
+ * every prerequisite is done; wave 1 = empty depends_on.
13
+ * Entries must be mappings with a non-empty string id — anything else is a
14
+ * GraphError naming the index, so consumers exit with a clean contract code
15
+ * instead of an uncaught TypeError on malformed tasks.yaml shapes. */
8
16
  export function buildGraph(tasks) {
9
17
  /** @type {Map<string, string[]>} prereq -> [dependent ids] */
10
18
  const dependents = new Map();
11
19
  /** @type {Map<string, string[]>} id -> prerequisite ids */
12
20
  const prerequisites = new Map();
13
21
  const byId = new Map();
14
- for (const t of tasks) {
22
+ for (const [i, t] of tasks.entries()) {
23
+ if (!isTaskMapping(t)) throw new GraphError(`tasks[${i}]: not a mapping (task entries must be 'key: value' mappings)`);
24
+ if (typeof t.id !== 'string' || t.id.length === 0) throw new GraphError(`tasks[${i}]: missing string 'id' (every task needs a non-empty string id)`);
15
25
  if (byId.has(t.id)) throw new GraphError(`duplicate task id: ${t.id}`);
16
26
  byId.set(t.id, t);
17
27
  prerequisites.set(t.id, Array.isArray(t.depends_on) ? t.depends_on : []);
@@ -169,7 +179,9 @@ function globsMayOverlap(a, b) {
169
179
  export function dedupeDependencies(tasks) {
170
180
  let duplicatesCollapsed = 0;
171
181
  const normalized = tasks.map((t) => {
172
- if (!Array.isArray(t.depends_on)) return t;
182
+ // Malformed entries pass through untouched; buildGraph owns reporting them
183
+ // (touching t.depends_on here would throw before the contract check runs).
184
+ if (!isTaskMapping(t) || !Array.isArray(t.depends_on)) return t;
173
185
  const seen = new Set();
174
186
  const deps = [];
175
187
  for (const d of t.depends_on) {
@@ -197,6 +209,9 @@ export function computeQualityStats(tasks) {
197
209
  let emptyOwnership = 0;
198
210
  let blankAcceptance = 0;
199
211
  for (const t of tasks) {
212
+ // Malformed entries are buildGraph's to report; counting them here would
213
+ // throw on property access before any consumer sees a contract error.
214
+ if (!isTaskMapping(t)) continue;
200
215
  if (Array.isArray(t.depends_on) && t.depends_on.includes(t.id)) selfDeps++;
201
216
  const own = t.file_ownership;
202
217
  if (!Array.isArray(own) || own.length === 0) emptyOwnership++;
@@ -3,9 +3,13 @@
3
3
  // Usage: node set-status.mjs <tasks.yaml> <taskId> <status> [--force]
4
4
  // ANY transition out of 'done' requires --force (reopening completed work —
5
5
  // directly or via a failed/blocked hop — needs explicit intent). Exit 4 on refusal.
6
- // Writes via temp-file + rename so concurrent readers never see partial files,
6
+ // Writes via O_EXCL temp-file + rename so concurrent readers never see partial
7
+ // files (same-wave parallel workers + crash mid-write both safe: the original
8
+ // stays byte-intact until the rename lands; a failure or kill before the rename
9
+ // leaves at worst an orphaned .tmp- sibling, never a half-written tasks.yaml),
7
10
  // and re-emits the file's dominant line-ending convention (CRLF stays CRLF).
8
- import { readFileSync, writeFileSync, renameSync } from 'node:fs';
11
+ // Exit codes: 0 ok · 4 usage/refusal/parse/IO errors.
12
+ import { readFileSync, writeFileSync, renameSync, unlinkSync } from 'node:fs';
9
13
  import { parseYaml, stringifyYaml } from './lib/yaml.mjs';
10
14
 
11
15
  const STATUSES = ['pending', 'ready', 'running', 'blocked', 'done', 'failed'];
@@ -54,7 +58,25 @@ if (prev === 'done' && nextStatus !== 'done' && !force) {
54
58
  }
55
59
 
56
60
  task.status = nextStatus;
57
- const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
58
- writeFileSync(tmp, stringifyYaml(data, comments, { eol }));
59
- renameSync(tmp, file); // atomic on POSIX: readers observe old-or-new, never partial
61
+ // O_EXCL ('wx') guarantees the temp path is freshly created — a stale leftover
62
+ // with the same pid+timestamp is never silently truncated and overwritten.
63
+ const content = stringifyYaml(data, comments, { eol });
64
+ let tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
65
+ for (let attempt = 0; ; attempt++) {
66
+ try {
67
+ writeFileSync(tmp, content, { flag: 'wx' });
68
+ break;
69
+ } catch (e) {
70
+ if (e?.code === 'EEXIST' && attempt < 10) { tmp = `${file}.tmp-${process.pid}-${Date.now()}-${attempt + 1}`; continue; }
71
+ console.error(`${file}: cannot write temp file (${e?.code ?? e?.message ?? e})`);
72
+ process.exit(4);
73
+ }
74
+ }
75
+ try {
76
+ renameSync(tmp, file); // atomic on POSIX: readers observe old-or-new, never partial
77
+ } catch (e) {
78
+ try { unlinkSync(tmp); } catch { /* original intact; leftover tmp is harmless */ }
79
+ console.error(`${file}: atomic rename failed (${e?.code ?? e?.message ?? e}) — original left untouched`);
80
+ process.exit(4);
81
+ }
60
82
  console.log(`${taskId}: ${prev} -> ${nextStatus}`);
@@ -9,7 +9,7 @@
9
9
  // disappeared are pruned by default (--prune is accepted as the explicit
10
10
  // no-op spelling of the same behavior). Writes atomically via temp + rename.
11
11
  // Exit codes: 0 ok · 2 no .archgen/ dir · 4 usage/IO/marker errors.
12
- import { existsSync, readFileSync, readdirSync, renameSync, statSync, writeFileSync } from 'node:fs';
12
+ import { existsSync, readFileSync, readdirSync, renameSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
13
13
  import { join } from 'node:path';
14
14
  import { parseYaml } from './lib/yaml.mjs';
15
15
 
@@ -194,9 +194,18 @@ for (const r of ordered) {
194
194
  else unchanged++;
195
195
  }
196
196
 
197
+ // O_EXCL ('wx') guarantees the temp path is freshly created; the original
198
+ // AGENTS.md stays byte-intact until the rename lands (crash mid-write leaves
199
+ // at worst an orphaned .tmp- sibling, never a half-written registry).
197
200
  const tmp = `${agentsPath}.tmp-${process.pid}-${Date.now()}`;
198
- writeFileSync(tmp, out);
199
- renameSync(tmp, agentsPath); // atomic on POSIX: readers observe old-or-new, never partial
201
+ try {
202
+ writeFileSync(tmp, out, { flag: 'wx' });
203
+ renameSync(tmp, agentsPath); // atomic on POSIX: readers observe old-or-new, never partial
204
+ } catch (e) {
205
+ try { unlinkSync(tmp); } catch { /* original intact; leftover tmp is harmless */ }
206
+ console.error(`${agentsPath}: atomic write failed (${e?.code ?? e?.message ?? e}) — original left untouched`);
207
+ process.exit(4);
208
+ }
200
209
 
201
210
  console.log(banner);
202
211
  for (const n of notes) console.log(n);