godpowers 3.13.1 → 3.13.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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,50 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [3.13.2] - 2026-06-17
11
+
12
+ Maintenance release that drives a third self-audit (`codeaudit.md`) to zero: one
13
+ Medium and twelve Low findings across de-duplication, error handling, security
14
+ hardening, the test gate, and docs. No public command/agent/workflow/recipe
15
+ surface change (counts stay 120 / 40 / 13 / 44); lib module count 90 -> 91.
16
+
17
+ ### Changed
18
+ - **Shared sync check-builder (ARC-001):** the four `*-sync` modules no longer
19
+ copy-paste `addCheck`/`listFiles`; they share `lib/sync-check.js` (full
20
+ `addCheck` for the aggregator, area-bound `makeAddCheck` for the rest).
21
+ - **Per-file coverage floor (TEST-001):** `coverage:lib` now emits a json-summary
22
+ and `scripts/check-per-file-coverage.js` (in `release:check`) fails any lib
23
+ module below 70% lines (excluding the two environment-bound browser drivers),
24
+ so a single file can no longer rot while the aggregate stays green.
25
+ - **De-duplication and cleanup (QUAL-001/002/003):** removed dead helpers
26
+ (two unused `rel()`, an unused `sha`), added `sync-fs.readTextOrNull` adopted by
27
+ `requirements.js` (which now sources PRD/ROADMAP paths from `artifact-map`), and
28
+ fixed a boolean/string status wart in `repo-surface-sync`.
29
+ - **Pillars delineation (ARC-002):** `pillars.js` now has section dividers
30
+ separating the model and artifact-sync halves (a full split was deferred; the
31
+ halves share construction functions that are public API).
32
+
33
+ ### Fixed
34
+ - **Reverse-sync error visibility (ERR-001):** the requirements step now writes
35
+ state before the ledger and surfaces a caught error as `requirementsError`
36
+ instead of silently nulling it.
37
+
38
+ ### Security
39
+ - **MCP module-name guard (SEC-001):** `requireRuntime` rejects any name that is
40
+ not a plain lib basename (defense-in-depth).
41
+ - **YAML recursion cap (SEC-002):** `intent.cleanArrays` caps recursion depth so a
42
+ pathologically deep file cannot overflow the stack.
43
+
44
+ ### Performance
45
+ - **have-nots regex (PERF-001):** `findPositions` compiles its regex once instead
46
+ of per line. The whole-ledger read in `evidence.readJsonl` is documented as
47
+ bounded/acceptable with an opt-in prune noted for the future (PERF-002).
48
+
49
+ ### Docs
50
+ - **Absolute README doc links (DOC-001):** `docs/` is deliberately excluded from
51
+ the package, so the README's `docs/*` links are now absolute GitHub URLs that
52
+ resolve on the npm page and in the tarball.
53
+
10
54
  ## [3.13.1] - 2026-06-16
11
55
 
12
56
  Maintenance release that drives a full self-audit (`codeaudit.md`) to zero: one
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![CI](https://github.com/aihxp/godpowers/actions/workflows/ci.yml/badge.svg)](https://github.com/aihxp/godpowers/actions/workflows/ci.yml)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
5
- [![Version](https://img.shields.io/badge/version-3.13.1-blue)](CHANGELOG.md)
5
+ [![Version](https://img.shields.io/badge/version-3.13.2-blue)](CHANGELOG.md)
6
6
  [![npm](https://img.shields.io/npm/v/godpowers.svg)](https://www.npmjs.com/package/godpowers)
7
7
 
8
8
  **Ship fast. Ship right. Ship everything. Ship accountably.**
@@ -12,30 +12,33 @@ idea to hardened production. It runs as **slash commands inside your AI coding
12
12
  tool** (Claude Code, Codex, Cursor, etc.) that orchestrate **specialist agents**
13
13
  in fresh contexts to do the work.
14
14
 
15
- Want the short proof first? Start with [Quick Proof](docs/quick-proof.md) to
15
+ Want the short proof first? Start with [Quick Proof](https://github.com/aihxp/godpowers/blob/main/docs/quick-proof.md) to
16
16
  run `npx godpowers quick-proof --project=. --brief`, see outcome metrics, pick
17
17
  a starter command set, and understand runtime expectations before reading the
18
- full reference. The [First 10 Minute Proof Case Study](docs/case-studies/first-10-minute-proof.md)
18
+ full reference. The [First 10 Minute Proof Case Study](https://github.com/aihxp/godpowers/blob/main/docs/case-studies/first-10-minute-proof.md)
19
19
  shows the same evidence as a before-and-after adoption story. External
20
- CLI-verifiable canaries now cover [sindresorhus/is](docs/case-studies/sindresorhus-is-adoption-canary.md),
21
- [expressjs/cors](docs/case-studies/expressjs-cors-adoption-canary.md), and
22
- [tinyhttp/tinyhttp](docs/case-studies/tinyhttp-adoption-canary.md), with host
20
+ CLI-verifiable canaries now cover [sindresorhus/is](https://github.com/aihxp/godpowers/blob/main/docs/case-studies/sindresorhus-is-adoption-canary.md),
21
+ [expressjs/cors](https://github.com/aihxp/godpowers/blob/main/docs/case-studies/expressjs-cors-adoption-canary.md), and
22
+ [tinyhttp/tinyhttp](https://github.com/aihxp/godpowers/blob/main/docs/case-studies/tinyhttp-adoption-canary.md), with host
23
23
  slash-command gaps called out rather than hidden.
24
- Host-run proof studies now cover [slugify-cli](docs/case-studies/run-a.md),
25
- [Countdown](docs/case-studies/run-b.md), and
26
- [react-github-readme-button](docs/case-studies/run-c.md), including one
24
+ Host-run proof studies now cover [slugify-cli](https://github.com/aihxp/godpowers/blob/main/docs/case-studies/run-a.md),
25
+ [Countdown](https://github.com/aihxp/godpowers/blob/main/docs/case-studies/run-b.md), and
26
+ [react-github-readme-button](https://github.com/aihxp/godpowers/blob/main/docs/case-studies/run-c.md), including one
27
27
  blocked harden run recorded as evidence instead of hidden as success.
28
28
 
29
29
  Godpowers makes AI coding accountable: every serious run should leave disk
30
30
  state, artifacts, validation gates, host guarantees, and a next action. Code is
31
31
  only one output. The project memory and proof trail matter too.
32
32
 
33
- Version 3.13.1 is a maintenance release that drives a full self-audit to zero:
34
- the runtime ledger no longer loses verification records under concurrent writes,
35
- the `outcome check` verifier and the pre-tool-use hook are hardened, the
36
- `*-sync` modules share one filesystem helper, the argument parser is table-driven,
37
- branch coverage is now gated, and the architecture map is kept in lockstep by a
38
- machine guard. Version 3.13.0 makes the default greenfield arc (`/god-mode`) miss
33
+ Version 3.13.2 is a maintenance release that drives a third self-audit to zero:
34
+ the `*-sync` modules now share one check-builder, the coverage gate enforces a
35
+ per-file floor, the corrupt-state error is typed, dead helpers are removed, and
36
+ the MCP module loader and YAML parser gain defense-in-depth guards. Version
37
+ 3.13.1 drove a full self-audit to zero: the runtime ledger no longer loses
38
+ verification records under concurrent writes, the `outcome check` verifier and
39
+ the pre-tool-use hook are hardened, the `*-sync` modules share one filesystem
40
+ helper, the argument parser is table-driven, and branch coverage is gated.
41
+ Version 3.13.0 makes the default greenfield arc (`/god-mode`) miss
39
42
  less. The one-shot `full-arc` workflow now runs a whole-codebase code audit after the
40
43
  build (so it catches what the per-slice reviews missed in AI-generated code) and
41
44
  a documentation pass after harden (so the shipped product has docs verified
@@ -183,7 +186,7 @@ instead of pretending a background agent ran.
183
186
  | Other install targets | Skills and agent contracts install, while host-native spawning depends on the tool. |
184
187
  | Degraded hosts | Godpowers must report local-only or simulated agent behavior instead of hiding the limitation. |
185
188
 
186
- See [Host capabilities](docs/host-capabilities.md) for the detailed guarantee
189
+ See [Host capabilities](https://github.com/aihxp/godpowers/blob/main/docs/host-capabilities.md) for the detailed guarantee
187
190
  model.
188
191
 
189
192
  ## Usage
@@ -334,17 +337,17 @@ dependency to the main `godpowers` package:
334
337
 
335
338
  ```bash
336
339
  npx godpowers mcp-info --project=.
337
- npx -y -p godpowers@3.13.1 -p @godpowers/mcp@3.13.1 godpowers-mcp serve --project=.
340
+ npx -y -p godpowers@3.13.2 -p @godpowers/mcp@3.13.2 godpowers-mcp serve --project=.
338
341
  ```
339
342
 
340
343
  The companion exposes `status`, `next`, `gate_check`, `lint_artifact`, and
341
344
  `trace_requirement`. Host registration is opt-in:
342
345
 
343
346
  ```bash
344
- npx -y -p godpowers@3.13.1 -p @godpowers/mcp@3.13.1 godpowers-mcp setup --host=codex --project=. --write
347
+ npx -y -p godpowers@3.13.2 -p @godpowers/mcp@3.13.2 godpowers-mcp setup --host=codex --project=. --write
345
348
  ```
346
349
 
347
- See [MCP Companion](docs/mcp.md) for package boundaries and setup details.
350
+ See [MCP Companion](https://github.com/aihxp/godpowers/blob/main/docs/mcp.md) for package boundaries and setup details.
348
351
 
349
352
  ### Slash Commands
350
353
 
@@ -618,16 +621,16 @@ Pi. T3 Code inherits from the underlying agent (Codex / Claude / OpenCode).
618
621
 
619
622
  ## Full reference
620
623
 
621
- - [Getting Started](docs/getting-started.md)
622
- - [Quick Proof](docs/quick-proof.md)
623
- - [First 10 Minute Proof Case Study](docs/case-studies/first-10-minute-proof.md)
624
- - [Concepts](docs/concepts.md)
625
- - [Command reference (all 120 skills + 40 agents)](docs/reference.md)
626
- - [Feature awareness](docs/feature-awareness.md)
627
- - [Adoption Canary](docs/adoption-canary.md)
628
- - [Repository documentation sync](docs/repo-doc-sync.md)
629
- - [Repository surface sync](docs/repo-surface-sync.md)
630
- - [Roadmap](docs/ROADMAP.md)
624
+ - [Getting Started](https://github.com/aihxp/godpowers/blob/main/docs/getting-started.md)
625
+ - [Quick Proof](https://github.com/aihxp/godpowers/blob/main/docs/quick-proof.md)
626
+ - [First 10 Minute Proof Case Study](https://github.com/aihxp/godpowers/blob/main/docs/case-studies/first-10-minute-proof.md)
627
+ - [Concepts](https://github.com/aihxp/godpowers/blob/main/docs/concepts.md)
628
+ - [Command reference (all 120 skills + 40 agents)](https://github.com/aihxp/godpowers/blob/main/docs/reference.md)
629
+ - [Feature awareness](https://github.com/aihxp/godpowers/blob/main/docs/feature-awareness.md)
630
+ - [Adoption Canary](https://github.com/aihxp/godpowers/blob/main/docs/adoption-canary.md)
631
+ - [Repository documentation sync](https://github.com/aihxp/godpowers/blob/main/docs/repo-doc-sync.md)
632
+ - [Repository surface sync](https://github.com/aihxp/godpowers/blob/main/docs/repo-surface-sync.md)
633
+ - [Roadmap](https://github.com/aihxp/godpowers/blob/main/docs/ROADMAP.md)
631
634
  - [Release Notes](RELEASE.md)
632
635
  - [Changelog](CHANGELOG.md)
633
636
  - [Inspiration](INSPIRATION.md)
package/RELEASE.md CHANGED
@@ -1,41 +1,40 @@
1
- # Godpowers 3.13.1 Release
1
+ # Godpowers 3.13.2 Release
2
2
 
3
3
  > Status: Prepared
4
- > Date: 2026-06-16
4
+ > Date: 2026-06-17
5
5
 
6
- [DECISION] Godpowers 3.13.1 is a maintenance release that drives a full self-audit (`codeaudit.md`, codeauditor-grade, nine weighted dimensions) to zero. It fixes one High finding plus the Medium and Low findings across runtime correctness, security hardening, the test gate, documentation, and de-duplication.
7
- [DECISION] No new skill, agent, workflow, or recipe surface is added or removed. Surface counts are unchanged from 3.13.0: 120 slash commands, 40 specialist agents, 13 workflows, 44 recipes.
8
- [DECISION] This release keeps `core` as the omitted installer profile, keeps `--profile=full` as the complete compatibility surface, and keeps the full 3.1.0-3.13.0 surface (fusion + codeauditor-grade audit + remediation loop + audited/documented greenfield arc).
6
+ [DECISION] Godpowers 3.13.2 is a maintenance release that drives a third self-audit (`codeaudit.md`, codeauditor-grade, nine weighted dimensions) to zero. It fixes one Medium finding and twelve Low findings across de-duplication, error handling, security hardening, the test gate, and documentation.
7
+ [DECISION] No new skill, agent, workflow, or recipe surface is added or removed. Surface counts are unchanged from 3.13.1: 120 slash commands, 40 specialist agents, 13 workflows, 44 recipes. The lib module count rises from 90 to 91 (`lib/sync-check.js`).
8
+ [DECISION] This release keeps `core` as the omitted installer profile, keeps `--profile=full` as the complete compatibility surface, and keeps the full 3.1.0-3.13.1 surface.
9
9
 
10
10
  ## What's in this release
11
11
 
12
- - [DECISION] Runtime correctness: `lib/evidence.js` `appendJsonlAtomic` now appends with `fs.appendFileSync` (O_APPEND) instead of a read-modify-write, so concurrent `verify`/`outcome check` processes no longer lose ledger records and the append is no longer O(n) (ERR-001). A `maxBuffer` overflow is surfaced distinctly instead of as a plain failure (ERR-003).
13
- - [DECISION] Security hardening: the pre-tool-use hook is reframed as a best-effort advisory typo guard and matches more destructive-command variants (SEC-001); `outcome check` announces a disk-sourced verifier before running it (SEC-002); the `LEDGER-LOG.md` command echo masks obvious secret shapes and `SECURITY.md` documents the ledger and Codex-sandbox trust boundaries (SEC-003, SEC-004); `SECURITY.md` replaces the non-existent `npm install --verify` with `npm audit signatures` (DOC-002).
14
- - [DECISION] Test gate: `coverage:lib` now enforces `--branches 75` (TEST-001); a new `scripts/test-runtime-audit.js` raises `lib/runtime-audit.js` line coverage from 68.8% to 77.8% (TEST-002); `scripts/test-router.js` no longer shares cumulative state across tests and cleans up its temp dirs (TEST-003); new `scripts/test-hooks.js`, `scripts/test-cli-log.js`, and `scripts/test-text-util.js` cover the new code.
15
- - [DECISION] De-duplication: the five `*-sync` modules share `lib/sync-fs.js`; the ANSI logger moves to `lib/cli-log.js` and `slugify` to `lib/text-util.js`; `installer-args.parseArgs` is now table-driven (ARC-001, QUAL-001, QUAL-002).
16
- - [DECISION] Documentation: `ARCHITECTURE-MAP.md` counts are regenerated and now machine-guarded by `scripts/test-doc-surface-counts.js`; `state.STATE_FILE` is the canonical state-file constant and `artifact-map.js`'s scope is documented accurately (DOC-001, DOC-003, ARC-002).
17
- - [DECISION] Re-audit follow-ups: a fresh self-audit confirmed no regressions and closed the residual gaps it found - `installer-core.js` imports the shared logger (QUAL-003); `dashboard.js`/`planning-systems.js` consume `sync-fs` (ARC-003); the `lib/README` module catalog is complete and now guarded by a completeness check (DOC-004); the ledger-append comment is corrected (DOC-005); the corrupt-state error is typed rather than message-matched (ERR-004); and the hook tests assert each warning's text (TEST-005).
12
+ - [DECISION] De-duplication (ARC-001, QUAL-001/002/003): the four `*-sync` modules share `lib/sync-check.js` (`addCheck`/`makeAddCheck`/`listFiles`) instead of copy-pasting them; removed dead helpers (two unused `rel()`, an unused `sha`); added `sync-fs.readTextOrNull` adopted by `requirements.js`, which now sources PRD/ROADMAP paths from `artifact-map`; fixed a boolean/string status wart in `repo-surface-sync`.
13
+ - [DECISION] Test gate (TEST-001, TEST-002): `coverage:lib` now emits a json-summary and `scripts/check-per-file-coverage.js` (in `release:check`) fails any lib module below 70% lines, excluding the two environment-bound browser drivers, so a single file can no longer rot while the aggregate stays green; the `run()`/`appendLog()` write path of the three sync siblings is now tested for the no-banned-dash invariant.
14
+ - [DECISION] Error handling (ERR-001): reverse-sync writes state before the ledger and surfaces a caught error as `requirementsError` instead of silently nulling it.
15
+ - [DECISION] Security hardening (SEC-001, SEC-002): the MCP `requireRuntime` rejects any module name that is not a plain lib basename; `intent.cleanArrays` caps recursion depth so a pathologically deep YAML cannot overflow the stack.
16
+ - [DECISION] Performance and docs (PERF-001/002, DOC-001, ARC-002): `have-nots` `findPositions` compiles its regex once per call; the bounded whole-ledger read is documented with an opt-in prune noted; the README's `docs/*` links are now absolute GitHub URLs (docs are deliberately excluded from the package); and `pillars.js` is delineated into its model and artifact-sync halves (a full split was deferred because the halves share public-API construction functions).
18
17
 
19
18
  ## Changes
20
19
 
21
- - [DECISION] `package.json`, `package-lock.json`, and `packages/mcp/package.json` now publish the 3.13.1 version.
22
- - [DECISION] New runtime modules `lib/sync-fs.js`, `lib/cli-log.js`, and `lib/text-util.js` (lib module count 87 -> 90). No public command/agent/workflow/recipe surface change.
23
- - [DECISION] CHANGELOG, RELEASE notes, README, roadmap, reference, architecture, and the architecture map now reflect 3.13.1. The SECURITY supported-version table already carries the `3.13.x` row.
20
+ - [DECISION] `package.json`, `package-lock.json`, and `packages/mcp/package.json` now publish the 3.13.2 version.
21
+ - [DECISION] New runtime module `lib/sync-check.js` (lib module count 90 -> 91). No public command/agent/workflow/recipe surface change.
22
+ - [DECISION] CHANGELOG, RELEASE notes, README, roadmap, reference, architecture, and the architecture map now reflect 3.13.2. The SECURITY supported-version table already carries the `3.13.x` row.
24
23
 
25
24
  ## Validation
26
25
 
27
26
  - [DECISION] `npm test` passed all command groups.
28
- - [DECISION] `npm run release:check` passed `coverage:lib` above the 90 percent line floor and the new 75 percent branch floor for `lib/**/*.js`.
27
+ - [DECISION] `npm run release:check` passed `coverage:lib` above the 90 percent line floor and the 75 percent branch floor, and the new per-file floor (>= 70 percent lines across 88 lib modules).
29
28
  - [DECISION] `npm run release:check` passed `npm audit --omit=dev` with 0 vulnerabilities and `git diff --check`.
30
- - [DECISION] `npm run release:check` passed public surface docs for version 3.13.1 with 120 skills, 40 agents, 13 workflows, and 44 recipes.
29
+ - [DECISION] `npm run release:check` passed public surface docs for version 3.13.2 with 120 skills, 40 agents, 13 workflows, and 44 recipes.
31
30
  - [DECISION] `npm run release:check` passed root and `@godpowers/mcp` package contents.
32
31
 
33
32
  ## Upgrade
34
33
 
35
- - [DECISION] Use `npm install -g godpowers@3.13.1` or `npx godpowers@3.13.1`.
36
- - [DECISION] No migration is required. Existing projects are unaffected; the changes are internal correctness, security, test-gate, and maintainability fixes with no surface change.
34
+ - [DECISION] Use `npm install -g godpowers@3.13.2` or `npx godpowers@3.13.2`.
35
+ - [DECISION] No migration is required. The changes are internal de-duplication, error-visibility, security, test-gate, and documentation improvements with no surface change.
37
36
 
38
37
  ## Notes
39
38
 
40
- - [DECISION] The publish targets are npm `godpowers@3.13.1`, npm `@godpowers/mcp@3.13.1`, and GitHub release `https://github.com/aihxp/godpowers/releases/tag/v3.13.1`.
39
+ - [DECISION] The publish targets are npm `godpowers@3.13.2`, npm `@godpowers/mcp@3.13.2`, and GitHub release `https://github.com/aihxp/godpowers/releases/tag/v3.13.2`.
41
40
  - [DECISION] The tag-triggered GitHub publish workflow remains the preferred npm path because it publishes with provenance. This release has not been tagged or published to npm yet.
package/lib/README.md CHANGED
@@ -29,6 +29,7 @@ package-level integrations.
29
29
  | `atomic-write.js` | Write load-bearing files through temp-file validation and atomic rename. |
30
30
  | `fs-async.js` | Promise-based file read/write helpers for non-blocking runtime paths. |
31
31
  | `sync-fs.js` | Shared project-relative read/write/exists/readJson helpers for the `*-sync` modules. |
32
+ | `sync-check.js` | Shared check-builder (`addCheck`/`makeAddCheck`) and file-lister for the `*-sync` modules. |
32
33
 
33
34
  ## Events and observability
34
35
 
package/lib/evidence.js CHANGED
@@ -195,6 +195,10 @@ function appendJsonlAtomic(file, record) {
195
195
  return file;
196
196
  }
197
197
 
198
+ // Reads the whole ledger into memory. This is bounded and acceptable for a CLI:
199
+ // each record caps its stdout/stderr tails (TAIL_CHARS) so growth is slow, and
200
+ // every consumer reads it once per command, never in a loop. If a long-lived
201
+ // project's ledger ever grows large, add an opt-in prune/size cap here (PERF-002).
198
202
  function readJsonl(file) {
199
203
  let raw;
200
204
  try {
@@ -53,9 +53,13 @@ const LABEL_TAGS = ['DECISION', 'HYPOTHESIS', 'OPEN QUESTION', 'OPEN-QUESTION'];
53
53
  function findPositions(content, regex) {
54
54
  const positions = [];
55
55
  const lines = content.split('\n');
56
+ // Compile once and reuse across lines (PERF-001); reset lastIndex per line so
57
+ // matching is identical to a fresh per-line regex.
58
+ const flags = regex.flags.includes('g') ? regex.flags : regex.flags + 'g';
59
+ const localRegex = new RegExp(regex.source, flags);
56
60
  for (let i = 0; i < lines.length; i++) {
61
+ localRegex.lastIndex = 0;
57
62
  let match;
58
- const localRegex = new RegExp(regex.source, regex.flags.includes('g') ? regex.flags : regex.flags + 'g');
59
63
  while ((match = localRegex.exec(lines[i])) !== null) {
60
64
  positions.push({ line: i + 1, column: match.index + 1, matched: match[0] });
61
65
  }
package/lib/intent.js CHANGED
@@ -332,15 +332,20 @@ function splitInlineArray(text) {
332
332
  return parts;
333
333
  }
334
334
 
335
- function cleanArrays(obj) {
336
- if (Array.isArray(obj)) return obj.map(cleanArrays);
335
+ // Far beyond any legitimate config nesting; caps recursion so a hostile,
336
+ // thousands-deep YAML file cannot overflow the stack (SEC-002).
337
+ const MAX_CLEAN_DEPTH = 200;
338
+
339
+ function cleanArrays(obj, depth = 0) {
340
+ if (depth > MAX_CLEAN_DEPTH) return obj;
341
+ if (Array.isArray(obj)) return obj.map((v) => cleanArrays(v, depth + 1));
337
342
  if (obj && typeof obj === 'object') {
338
343
  // Detect array container (legacy or new)
339
- if (obj.__items__) return obj.__items__.map(cleanArrays);
344
+ if (obj.__items__) return obj.__items__.map((v) => cleanArrays(v, depth + 1));
340
345
  if (obj.__pending_array__) return obj.__pending_array__;
341
346
  const cleaned = {};
342
347
  for (const [k, v] of Object.entries(obj)) {
343
- cleaned[k] = cleanArrays(v);
348
+ cleaned[k] = cleanArrays(v, depth + 1);
344
349
  }
345
350
  return cleaned;
346
351
  }
package/lib/pillars.js CHANGED
@@ -114,6 +114,13 @@ const GODPOWERS_ARTIFACTS = [
114
114
  '.godpowers/design/PRODUCT.md'
115
115
  ];
116
116
 
117
+ // ===========================================================================
118
+ // Pillar model: parse pillar files, detect installed pillars, compute the
119
+ // per-task load set, and construct/initialize pillar files. Shared with the
120
+ // artifact-sync workflow below (init/ensurePillar/pillarStub/detect are also
121
+ // part of the public API).
122
+ // ===========================================================================
123
+
117
124
  function stripQuotes(value) {
118
125
  return String(value).trim().replace(/^['"]|['"]$/g, '');
119
126
  }
@@ -401,6 +408,12 @@ function writeFenced(filePath, begin, end, content) {
401
408
  fs.writeFileSync(filePath, next);
402
409
  }
403
410
 
411
+ // ===========================================================================
412
+ // Artifact-sync workflow: turn Godpowers artifacts (PRD/ARCH/...) into durable
413
+ // pillar signals and write them into the routed pillar files. Builds on the
414
+ // model above (init/ensurePillar/pillarStub/detect/buildProtocolContent).
415
+ // ===========================================================================
416
+
404
417
  function artifactToPillars(artifactPath) {
405
418
  const normalized = artifactPath.replace(/\\/g, '/');
406
419
  const pillars = [];
@@ -100,10 +100,6 @@ function ensureDir(filePath) {
100
100
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
101
101
  }
102
102
 
103
- function sha(input) {
104
- return crypto.createHash('sha256').update(input).digest('hex');
105
- }
106
-
107
103
  function hashFiles(projectRoot, files) {
108
104
  const h = crypto.createHash('sha256');
109
105
  for (const file of files.map((f) => f.path).sort()) {
@@ -473,7 +469,6 @@ module.exports = {
473
469
  _private: {
474
470
  classifyFile,
475
471
  extractSignals,
476
- filesForKinds,
477
- sha
472
+ filesForKinds
478
473
  }
479
474
  };
@@ -10,6 +10,9 @@ const path = require('path');
10
10
 
11
11
  const recipes = require('./recipes');
12
12
  const { read, write } = require('./sync-fs');
13
+ const { makeAddCheck } = require('./sync-check');
14
+
15
+ const addCheck = makeAddCheck('recipe-coverage');
13
16
 
14
17
  const LOG_PATH = '.godpowers/surface/RECIPE-COVERAGE-SYNC.md';
15
18
 
@@ -42,18 +45,6 @@ const REQUIRED_COVERAGE = [
42
45
  ];
43
46
 
44
47
 
45
- function addCheck(checks, id, status, relPath, message, opts = {}) {
46
- checks.push({
47
- area: 'recipe-coverage',
48
- id,
49
- status,
50
- path: relPath,
51
- message,
52
- severity: opts.severity || (status === 'fresh' ? 'info' : 'warning'),
53
- spawn: opts.spawn || null
54
- });
55
- }
56
-
57
48
  function recipePath(projectRoot, name) {
58
49
  const rel = `routing/recipes/${name}.yaml`;
59
50
  return fs.existsSync(path.join(projectRoot, rel)) ? rel : 'routing/recipes/';
@@ -10,6 +10,9 @@ const fs = require('fs');
10
10
  const path = require('path');
11
11
 
12
12
  const { read, write, readJson } = require('./sync-fs');
13
+ const { makeAddCheck } = require('./sync-check');
14
+
15
+ const addCheck = makeAddCheck('release-surface');
13
16
 
14
17
  const LOG_PATH = '.godpowers/surface/RELEASE-SURFACE-SYNC.md';
15
18
 
@@ -46,18 +49,6 @@ function releaseGateText(projectRoot, pkg) {
46
49
  ].join('\n');
47
50
  }
48
51
 
49
- function addCheck(checks, id, status, relPath, message, opts = {}) {
50
- checks.push({
51
- area: 'release-surface',
52
- id,
53
- status,
54
- path: relPath,
55
- message,
56
- severity: opts.severity || (status === 'fresh' ? 'info' : 'warning'),
57
- spawn: opts.spawn || null
58
- });
59
- }
60
-
61
52
  function detect(projectRoot) {
62
53
  const checks = [];
63
54
  const pkg = readJson(projectRoot, 'package.json') || {};
@@ -12,6 +12,7 @@ const path = require('path');
12
12
 
13
13
  const { parseSimpleYaml } = require('./intent');
14
14
  const { read, write, exists, readJson } = require('./sync-fs');
15
+ const { addCheck, listFiles } = require('./sync-check');
15
16
  const extensions = require('./extensions');
16
17
  const repoDocSync = require('./repo-doc-sync');
17
18
  const routeQualitySync = require('./route-quality-sync');
@@ -52,19 +53,6 @@ const REQUIRED_PACKAGE_CHECKS = [
52
53
  'routing/god-export-otel.yaml'
53
54
  ];
54
55
 
55
- function rel(projectRoot, absPath) {
56
- return path.relative(projectRoot, absPath).split(path.sep).join('/');
57
- }
58
-
59
- function listFiles(projectRoot, relDir, pattern) {
60
- const dir = path.join(projectRoot, relDir);
61
- if (!fs.existsSync(dir)) return [];
62
- return fs.readdirSync(dir)
63
- .filter((name) => pattern.test(name))
64
- .sort()
65
- .map((name) => `${relDir}/${name}`.replace(/\\/g, '/'));
66
- }
67
-
68
56
  function releaseGateText(projectRoot, pkg) {
69
57
  return [
70
58
  JSON.stringify((pkg && pkg.scripts) || {}),
@@ -81,19 +69,6 @@ function commandForSkill(skillPath) {
81
69
  return `/${path.basename(skillPath, '.md')}`;
82
70
  }
83
71
 
84
- function addCheck(checks, area, id, status, relPath, message, opts = {}) {
85
- checks.push({
86
- area,
87
- id,
88
- status,
89
- path: relPath,
90
- message,
91
- severity: opts.severity || (status === 'fresh' ? 'info' : 'warning'),
92
- safeFix: opts.safeFix === true,
93
- spawn: opts.spawn || null
94
- });
95
- }
96
-
97
72
  function routingChecks(projectRoot) {
98
73
  const checks = [];
99
74
  const skills = listFiles(projectRoot, 'skills', /^god.*\.md$/);
@@ -485,15 +460,12 @@ function releasePolicyChecks(projectRoot) {
485
460
  checks,
486
461
  'release',
487
462
  'release-checklist-surface-sync',
488
- read(projectRoot, 'docs/RELEASE-CHECKLIST.md').includes('repo-surface-sync'),
463
+ read(projectRoot, 'docs/RELEASE-CHECKLIST.md').includes('repo-surface-sync') ? 'fresh' : 'stale',
489
464
  'docs/RELEASE-CHECKLIST.md',
490
465
  'Release checklist references repo-surface-sync readiness.',
491
466
  { spawn: 'god-docs-writer' }
492
467
  );
493
- return checks.map((check) => ({
494
- ...check,
495
- status: check.status === true ? 'fresh' : (check.status === false ? 'stale' : check.status)
496
- }));
468
+ return checks;
497
469
  }
498
470
 
499
471
  function detect(projectRoot) {
@@ -33,9 +33,11 @@ const linkage = require('./linkage');
33
33
  const state = require('./state');
34
34
  const atomic = require('./atomic-write');
35
35
  const textUtil = require('./text-util');
36
+ const artifactMap = require('./artifact-map');
37
+ const { readTextOrNull: readText } = require('./sync-fs');
36
38
 
37
- const PRD_PATH = '.godpowers/prd/PRD.md';
38
- const ROADMAP_PATH = '.godpowers/roadmap/ROADMAP.md';
39
+ const PRD_PATH = artifactMap.requiredArtifactsForTier('prd')[0].path;
40
+ const ROADMAP_PATH = artifactMap.requiredArtifactsForTier('roadmap')[0].path;
39
41
  const LEDGER_PATH = '.godpowers/REQUIREMENTS.md';
40
42
 
41
43
  const PRIORITIES = ['MUST', 'SHOULD', 'COULD'];
@@ -44,16 +46,6 @@ const REQ_ID_RE_G = /\bP-(MUST|SHOULD|COULD)-\d+\b/g;
44
46
  const MILESTONE_ID_RE = /\bM-[\w-]+\b/;
45
47
  const LABEL_RE = /\[(?:DECISION|HYPOTHESIS|OPEN QUESTION)\]/g;
46
48
 
47
- function readText(projectRoot, relPath) {
48
- const file = path.join(projectRoot, relPath);
49
- if (!fs.existsSync(file)) return null;
50
- try {
51
- return fs.readFileSync(file, 'utf8');
52
- } catch (e) {
53
- return null;
54
- }
55
- }
56
-
57
49
  function pad2(n) {
58
50
  return String(n).padStart(2, '0');
59
51
  }
@@ -318,23 +318,29 @@ function run(projectRoot, opts = {}) {
318
318
  // write the file when the PRD actually declares requirements, to avoid
319
319
  // littering pre-PRD projects with an empty ledger.
320
320
  let requirementsSummary = null;
321
+ let requirementsError = null;
321
322
  if (opts.runRequirements !== false) {
322
323
  try {
323
324
  const derived = requirements.derive(projectRoot);
324
325
  if (derived.hasRequirements) {
325
- requirements.writeLedger(projectRoot, derived);
326
326
  const currentState = state.read(projectRoot);
327
327
  requirementsSummary = requirements.summarizeForState(
328
328
  derived,
329
329
  currentState && currentState.deliverables
330
330
  );
331
+ // Write state.json first, then the ledger, so a state-write failure
332
+ // cannot leave a REQUIREMENTS.md ledger that state.json never references.
331
333
  if (currentState) {
332
334
  currentState.deliverables = requirementsSummary;
333
335
  state.write(projectRoot, currentState);
334
336
  }
337
+ requirements.writeLedger(projectRoot, derived);
335
338
  }
336
339
  } catch (e) {
340
+ // ERR-001: surface the failure instead of swallowing it, so the caller can
341
+ // tell a genuine requirements-step error apart from "no requirements".
337
342
  requirementsSummary = null;
343
+ requirementsError = e.message;
338
344
  }
339
345
  }
340
346
 
@@ -346,7 +352,8 @@ function run(projectRoot, opts = {}) {
346
352
  footers,
347
353
  sourceSyncResult,
348
354
  reviewItems,
349
- requirements: requirementsSummary
355
+ requirements: requirementsSummary,
356
+ requirementsError
350
357
  };
351
358
  }
352
359
 
@@ -11,6 +11,9 @@ const path = require('path');
11
11
 
12
12
  const { parseSimpleYaml } = require('./intent');
13
13
  const { read, write } = require('./sync-fs');
14
+ const { makeAddCheck, listFiles } = require('./sync-check');
15
+
16
+ const addCheck = makeAddCheck('route-quality');
14
17
 
15
18
  const LOG_PATH = '.godpowers/surface/ROUTE-QUALITY-SYNC.md';
16
19
  const CONTEXTUAL_NEXT_VALUES = new Set([
@@ -101,15 +104,6 @@ const TIER_GATE_COMMANDS = new Set([
101
104
  '/god-harden'
102
105
  ]);
103
106
 
104
- function listFiles(projectRoot, relDir, pattern) {
105
- const dir = path.join(projectRoot, relDir);
106
- if (!fs.existsSync(dir)) return [];
107
- return fs.readdirSync(dir)
108
- .filter((name) => pattern.test(name))
109
- .sort()
110
- .map((name) => `${relDir}/${name}`.replace(/\\/g, '/'));
111
- }
112
-
113
107
  function arr(value) {
114
108
  return Array.isArray(value) ? value : [];
115
109
  }
@@ -122,18 +116,6 @@ function parseRoute(projectRoot, routePath) {
122
116
  }
123
117
  }
124
118
 
125
- function addCheck(checks, id, status, relPath, message, opts = {}) {
126
- checks.push({
127
- area: 'route-quality',
128
- id,
129
- status,
130
- path: relPath,
131
- message,
132
- severity: opts.severity || (status === 'fresh' ? 'info' : 'warning'),
133
- spawn: opts.spawn || null
134
- });
135
- }
136
-
137
119
  function spawnTokens(route) {
138
120
  const execution = route.execution || {};
139
121
  return normalizeSpawnList([
@@ -30,10 +30,6 @@ const SYSTEM_TARGETS = {
30
30
  }
31
31
  };
32
32
 
33
- function rel(projectRoot, absPath) {
34
- return path.relative(projectRoot, absPath).split(path.sep).join('/');
35
- }
36
-
37
33
  function sha(input) {
38
34
  return `sha256:${crypto.createHash('sha256').update(input).digest('hex')}`;
39
35
  }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Shared check-builder and file-lister for the lib/*-sync.js family (ARC-001).
3
+ *
4
+ * The aggregator (repo-surface-sync) passes a per-call `area` and may mark a
5
+ * check `safeFix`, so it uses the full `addCheck`. The single-area sync modules
6
+ * (recipe-coverage, release-surface, route-quality) bind their area once via
7
+ * `makeAddCheck(area)`; their records intentionally omit `safeFix` (none of
8
+ * their checks are auto-fixable), matching the original per-module builders.
9
+ */
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+
14
+ function severityFor(status, opts) {
15
+ return opts.severity || (status === 'fresh' ? 'info' : 'warning');
16
+ }
17
+
18
+ // Full form: caller supplies `area`; records include the `safeFix` flag.
19
+ function addCheck(checks, area, id, status, relPath, message, opts = {}) {
20
+ checks.push({
21
+ area,
22
+ id,
23
+ status,
24
+ path: relPath,
25
+ message,
26
+ severity: severityFor(status, opts),
27
+ safeFix: opts.safeFix === true,
28
+ spawn: opts.spawn || null
29
+ });
30
+ }
31
+
32
+ // Area-bound form for single-area modules; records omit `safeFix`.
33
+ function makeAddCheck(area) {
34
+ return function (checks, id, status, relPath, message, opts = {}) {
35
+ checks.push({
36
+ area,
37
+ id,
38
+ status,
39
+ path: relPath,
40
+ message,
41
+ severity: severityFor(status, opts),
42
+ spawn: opts.spawn || null
43
+ });
44
+ };
45
+ }
46
+
47
+ function listFiles(projectRoot, relDir, pattern) {
48
+ const dir = path.join(projectRoot, relDir);
49
+ if (!fs.existsSync(dir)) return [];
50
+ return fs.readdirSync(dir)
51
+ .filter((name) => pattern.test(name))
52
+ .sort()
53
+ .map((name) => `${relDir}/${name}`.replace(/\\/g, '/'));
54
+ }
55
+
56
+ module.exports = { addCheck, makeAddCheck, listFiles };
package/lib/sync-fs.js CHANGED
@@ -34,4 +34,16 @@ function readJson(projectRoot, relPath) {
34
34
  }
35
35
  }
36
36
 
37
- module.exports = { read, write, exists, readJson };
37
+ // Like read(), but returns null (not '') when the file is missing or unreadable,
38
+ // for callers that distinguish "absent" from "empty".
39
+ function readTextOrNull(projectRoot, relPath) {
40
+ const file = path.join(projectRoot, relPath);
41
+ if (!fs.existsSync(file)) return null;
42
+ try {
43
+ return fs.readFileSync(file, 'utf8');
44
+ } catch (err) {
45
+ return null;
46
+ }
47
+ }
48
+
49
+ module.exports = { read, write, exists, readJson, readTextOrNull };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "godpowers",
3
- "version": "3.13.1",
3
+ "version": "3.13.2",
4
4
  "description": "AI-powered development system: 120 slash commands and 40 specialist agents that take a project from raw idea to hardened production. Runs inside Claude Code, Codex, Cursor, Windsurf, Gemini, and 10+ other AI coding tools.",
5
5
  "bin": {
6
6
  "godpowers": "./bin/install.js"
@@ -24,11 +24,11 @@
24
24
  "test:e2e": "node tests/integration/full-arc.test.js",
25
25
  "test:mcp": "npm --workspace @godpowers/mcp test",
26
26
  "coverage": "c8 --reporter=text --reporter=lcov node scripts/run-tests.js",
27
- "coverage:lib": "c8 --include=lib/**/*.js --check-coverage --lines 90 --branches 75 --reporter=text node scripts/run-tests.js",
27
+ "coverage:lib": "c8 --include=lib/**/*.js --check-coverage --lines 90 --branches 75 --reporter=text --reporter=json-summary node scripts/run-tests.js",
28
28
  "test:audit": "npm audit --omit=dev && git diff --check && npm run test:surface",
29
29
  "pack:check": "node scripts/check-package-contents.js",
30
30
  "pack:mcp:check": "npm --workspace @godpowers/mcp run pack:check",
31
- "release:check": "npm run coverage:lib && npm run test:audit && npm run pack:check && npm run pack:mcp:check",
31
+ "release:check": "npm run coverage:lib && node scripts/check-per-file-coverage.js && npm run test:audit && npm run pack:check && npm run pack:mcp:check",
32
32
  "lint": "node scripts/static-check.js"
33
33
  },
34
34
  "workspaces": [