forgedock 1.0.16 → 1.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/README.md +54 -3
  2. package/bin/cross-publish.mjs +344 -0
  3. package/bin/forgedock.mjs +2205 -541
  4. package/bin/github-app-manifest.json.license +2 -0
  5. package/bin/hooks/session-start.mjs +4 -5
  6. package/bin/registry.mjs +20 -0
  7. package/bin/tui.mjs +172 -96
  8. package/commands/adopt.md +433 -0
  9. package/commands/analytics.md +2 -0
  10. package/commands/audit-agents.md +2 -0
  11. package/commands/audit.md +2 -0
  12. package/commands/autopilot.md +77 -0
  13. package/commands/changelog.md +354 -0
  14. package/commands/cleanup.md +2 -0
  15. package/commands/compat-audit.md +330 -0
  16. package/commands/deploy-info.md +6 -5
  17. package/commands/design-bench.md +176 -0
  18. package/commands/design-render-critique-loop.md +103 -0
  19. package/commands/design.md +259 -0
  20. package/commands/diagnose.md +483 -0
  21. package/commands/explain.md +349 -0
  22. package/commands/forge-stats.md +2 -0
  23. package/commands/forgedock-init.md +2 -0
  24. package/commands/geo-audit.md +2 -0
  25. package/commands/incident-response.md +2 -0
  26. package/commands/issue.md +9 -3
  27. package/commands/milestone.md +2 -0
  28. package/commands/optimize.md +411 -0
  29. package/commands/orchestrate.md +239 -0
  30. package/commands/pipeline-health.md +60 -1
  31. package/commands/qa-sweep.md +39 -2
  32. package/commands/quality-gate.md +67 -0
  33. package/commands/replay.md +263 -0
  34. package/commands/resume.md +264 -0
  35. package/commands/review-pr-agents.md +5 -1
  36. package/commands/review-pr-staging.md +160 -2
  37. package/commands/review-pr.md +85 -3
  38. package/commands/rollback.md +2 -0
  39. package/commands/scope.md +280 -0
  40. package/commands/security-audit.md +2 -0
  41. package/commands/status.md +262 -0
  42. package/commands/sync-ecosystem.md +2 -0
  43. package/commands/test-gate.md +929 -0
  44. package/commands/validate.md +2 -0
  45. package/commands/work-on/build/architect.md +128 -18
  46. package/commands/work-on/build/context.md +130 -17
  47. package/commands/work-on/build/implement.md +7 -3
  48. package/commands/work-on/build/validate.md +2 -0
  49. package/commands/work-on/build.md +32 -7
  50. package/commands/work-on/close.md +85 -0
  51. package/commands/work-on/decompose.md +2 -0
  52. package/commands/work-on/investigate.md +30 -3
  53. package/commands/work-on/review.md +13 -1
  54. package/commands/work-on-monolithic.md +2 -0
  55. package/commands/work-on.md +676 -38
  56. package/docs/BRAND.md +51 -0
  57. package/docs/CI.md +177 -0
  58. package/docs/CONFIG.md +94 -0
  59. package/docs/FORGE-PROTOCOL.md +324 -0
  60. package/docs/articles/b-vs-c-baseline.md +210 -0
  61. package/docs/articles/full-abc-benchmark.md +205 -0
  62. package/docs/articles/per-repo-adaptive-scripts-token-savings.md +174 -0
  63. package/docs/articles/selective-spec-loading-token-savings.md +140 -0
  64. package/docs/claude-breakpoints.json +47 -0
  65. package/docs/design/abc-benchmark.md +195 -0
  66. package/docs/design/design-architect-rationale.md +123 -0
  67. package/docs/design/design-memory.md +61 -0
  68. package/docs/design/design-spec-schema.md +248 -0
  69. package/docs/design/design-system-lint.md +150 -0
  70. package/docs/design/divergent-generation.md +106 -0
  71. package/docs/design/effects-appropriateness.md +133 -0
  72. package/docs/design/fixtures/briefs/README.md +47 -0
  73. package/docs/design/fixtures/briefs/cadence.md +39 -0
  74. package/docs/design/fixtures/briefs/plume.md +39 -0
  75. package/docs/design/fixtures/briefs/slipstream.md +39 -0
  76. package/docs/design/fixtures/briefs/tender.md +39 -0
  77. package/docs/design/fixtures/briefs/voltage.md +39 -0
  78. package/docs/design/fixtures/runs/b-vs-c-baseline/cadence/arm-b/run-1.html +364 -0
  79. package/docs/design/fixtures/runs/b-vs-c-baseline/cadence/arm-b/run-2.html +420 -0
  80. package/docs/design/fixtures/runs/b-vs-c-baseline/cadence/arm-b/run-3.html +506 -0
  81. package/docs/design/fixtures/runs/b-vs-c-baseline/plume/arm-b/run-1.html +611 -0
  82. package/docs/design/fixtures/runs/b-vs-c-baseline/plume/arm-b/run-2.html +594 -0
  83. package/docs/design/fixtures/runs/b-vs-c-baseline/plume/arm-b/run-3.html +817 -0
  84. package/docs/design/fixtures/runs/b-vs-c-baseline/runs.json +175 -0
  85. package/docs/design/fixtures/runs/b-vs-c-baseline/scorecard.json +138 -0
  86. package/docs/design/fixtures/runs/b-vs-c-baseline/slipstream/arm-b/run-1.html +373 -0
  87. package/docs/design/fixtures/runs/b-vs-c-baseline/slipstream/arm-b/run-2.html +398 -0
  88. package/docs/design/fixtures/runs/b-vs-c-baseline/slipstream/arm-b/run-3.html +595 -0
  89. package/docs/design/fixtures/runs/b-vs-c-baseline/tender/arm-b/run-1.html +531 -0
  90. package/docs/design/fixtures/runs/b-vs-c-baseline/tender/arm-b/run-2.html +565 -0
  91. package/docs/design/fixtures/runs/b-vs-c-baseline/tender/arm-b/run-3.html +674 -0
  92. package/docs/design/fixtures/runs/b-vs-c-baseline/voltage/arm-b/run-1.html +388 -0
  93. package/docs/design/fixtures/runs/b-vs-c-baseline/voltage/arm-b/run-2.html +463 -0
  94. package/docs/design/fixtures/runs/b-vs-c-baseline/voltage/arm-b/run-3.html +568 -0
  95. package/docs/design/fixtures/runs/full-abc/cadence/arm-a/run-1.html +562 -0
  96. package/docs/design/fixtures/runs/full-abc/cadence/arm-a/run-2.html +618 -0
  97. package/docs/design/fixtures/runs/full-abc/cadence/arm-a/run-3.html +618 -0
  98. package/docs/design/fixtures/runs/full-abc/cadence/arm-b/run-1.html +364 -0
  99. package/docs/design/fixtures/runs/full-abc/cadence/arm-b/run-2.html +420 -0
  100. package/docs/design/fixtures/runs/full-abc/cadence/arm-b/run-3.html +506 -0
  101. package/docs/design/fixtures/runs/full-abc/plume/arm-a/run-1.html +527 -0
  102. package/docs/design/fixtures/runs/full-abc/plume/arm-a/run-2.html +445 -0
  103. package/docs/design/fixtures/runs/full-abc/plume/arm-a/run-3.html +483 -0
  104. package/docs/design/fixtures/runs/full-abc/plume/arm-b/run-1.html +611 -0
  105. package/docs/design/fixtures/runs/full-abc/plume/arm-b/run-2.html +594 -0
  106. package/docs/design/fixtures/runs/full-abc/plume/arm-b/run-3.html +817 -0
  107. package/docs/design/fixtures/runs/full-abc/runs.json +190 -0
  108. package/docs/design/fixtures/runs/full-abc/scorecard.json +144 -0
  109. package/docs/design/fixtures/runs/full-abc/slipstream/arm-a/run-1.html +496 -0
  110. package/docs/design/fixtures/runs/full-abc/slipstream/arm-a/run-2.html +447 -0
  111. package/docs/design/fixtures/runs/full-abc/slipstream/arm-a/run-3.html +564 -0
  112. package/docs/design/fixtures/runs/full-abc/slipstream/arm-b/run-1.html +373 -0
  113. package/docs/design/fixtures/runs/full-abc/slipstream/arm-b/run-2.html +398 -0
  114. package/docs/design/fixtures/runs/full-abc/slipstream/arm-b/run-3.html +595 -0
  115. package/docs/design/fixtures/runs/full-abc/tender/arm-a/run-1.html +622 -0
  116. package/docs/design/fixtures/runs/full-abc/tender/arm-a/run-2.html +475 -0
  117. package/docs/design/fixtures/runs/full-abc/tender/arm-a/run-3.html +660 -0
  118. package/docs/design/fixtures/runs/full-abc/tender/arm-b/run-1.html +531 -0
  119. package/docs/design/fixtures/runs/full-abc/tender/arm-b/run-2.html +565 -0
  120. package/docs/design/fixtures/runs/full-abc/tender/arm-b/run-3.html +674 -0
  121. package/docs/design/fixtures/runs/full-abc/voltage/arm-a/run-1.html +525 -0
  122. package/docs/design/fixtures/runs/full-abc/voltage/arm-a/run-2.html +446 -0
  123. package/docs/design/fixtures/runs/full-abc/voltage/arm-a/run-3.html +463 -0
  124. package/docs/design/fixtures/runs/full-abc/voltage/arm-b/run-1.html +388 -0
  125. package/docs/design/fixtures/runs/full-abc/voltage/arm-b/run-2.html +463 -0
  126. package/docs/design/fixtures/runs/full-abc/voltage/arm-b/run-3.html +568 -0
  127. package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-a/run-1.html +1068 -0
  128. package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-a/run-2.html +1423 -0
  129. package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-a/run-3.html +1245 -0
  130. package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-b/run-1.html +305 -0
  131. package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-b/run-2.html +327 -0
  132. package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-b/run-3.html +428 -0
  133. package/docs/design/fixtures/runs/full-abc-opus/plume/arm-a/run-1.html +1270 -0
  134. package/docs/design/fixtures/runs/full-abc-opus/plume/arm-a/run-2.html +1172 -0
  135. package/docs/design/fixtures/runs/full-abc-opus/plume/arm-a/run-3.html +1532 -0
  136. package/docs/design/fixtures/runs/full-abc-opus/plume/arm-b/run-1.html +315 -0
  137. package/docs/design/fixtures/runs/full-abc-opus/plume/arm-b/run-2.html +342 -0
  138. package/docs/design/fixtures/runs/full-abc-opus/plume/arm-b/run-3.html +353 -0
  139. package/docs/design/fixtures/runs/full-abc-opus/runs.json +190 -0
  140. package/docs/design/fixtures/runs/full-abc-opus/scorecard.json +144 -0
  141. package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-a/run-1.html +345 -0
  142. package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-a/run-2.html +396 -0
  143. package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-a/run-3.html +502 -0
  144. package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-b/run-1.html +408 -0
  145. package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-b/run-2.html +446 -0
  146. package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-b/run-3.html +496 -0
  147. package/docs/design/fixtures/runs/full-abc-opus/tender/arm-a/run-1.html +813 -0
  148. package/docs/design/fixtures/runs/full-abc-opus/tender/arm-a/run-2.html +711 -0
  149. package/docs/design/fixtures/runs/full-abc-opus/tender/arm-a/run-3.html +721 -0
  150. package/docs/design/fixtures/runs/full-abc-opus/tender/arm-b/run-1.html +280 -0
  151. package/docs/design/fixtures/runs/full-abc-opus/tender/arm-b/run-2.html +291 -0
  152. package/docs/design/fixtures/runs/full-abc-opus/tender/arm-b/run-3.html +366 -0
  153. package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-a/run-1.html +1010 -0
  154. package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-a/run-2.html +836 -0
  155. package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-a/run-3.html +930 -0
  156. package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-b/run-1.html +407 -0
  157. package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-b/run-2.html +396 -0
  158. package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-b/run-3.html +529 -0
  159. package/docs/design/foundations/brutalist.css +294 -0
  160. package/docs/design/foundations/editorial.css +300 -0
  161. package/docs/design/foundations/minimal-luxury.css +295 -0
  162. package/docs/design/foundations/technical.css +293 -0
  163. package/docs/design/foundations/warm-photo.css +299 -0
  164. package/docs/design/reference-corpus.md +1726 -0
  165. package/docs/design/render-critique-loop.md +57 -0
  166. package/docs/site/getting-started.md +21 -0
  167. package/docs/spec-graph-schema.md +397 -0
  168. package/package.json +3 -3
  169. package/scripts/bench-scorecard.mjs +239 -0
  170. package/scripts/build-spec-graph.mjs +509 -0
  171. package/scripts/classify-lane.sh +138 -0
  172. package/scripts/design-system-lint.mjs +993 -0
  173. package/scripts/graph-query.sh +486 -0
  174. package/scripts/transition-label.sh +169 -0
  175. package/scripts/validate-pr-target.sh +30 -0
  176. package/scripts/validate-spec-graph.sh +423 -0
  177. package/templates/devdocs/README.md +72 -0
  178. package/templates/devdocs/agent/using-forgedock.md +3 -0
  179. package/templates/devdocs/agent/using-github.md +3 -0
  180. package/templates/devdocs/index.yaml +162 -0
  181. package/templates/devdocs/project/architecture.md +3 -0
  182. package/templates/devdocs/project/conventions.md +3 -0
  183. package/templates/devdocs/project/custom-instructions.md +3 -0
  184. package/templates/devdocs/project/glossary.md +3 -0
  185. package/templates/devdocs/project/stack.md +3 -0
  186. package/templates/devdocs/templates/commit-convention.md +3 -0
  187. package/templates/devdocs/templates/issue.md +3 -0
  188. package/templates/devdocs/templates/pull-request.md +3 -0
  189. package/templates/devdocs/templates/review-finding.md +3 -0
  190. package/templates/workflows/forgedock-review.yml +96 -0
@@ -0,0 +1,57 @@
1
+ # Render → vision-critique → iterate loop
2
+
3
+ > **Status**: committed methodology doc for the UI Taste Harness (#882).
4
+ > Executed by [`/design-render-critique-loop`](../../commands/design-render-critique-loop.md).
5
+ > Consumes: [`FORGE:DESIGN_SPEC`](design-spec-schema.md) (#881), the deterministic linter `scripts/design-system-lint.mjs` (#884), the perceptual negatives N3/N8–N12 in [reference-corpus](reference-corpus.md) (#880), the #878/#875 Playwright render protocol.
6
+ > Independent from the [ABC benchmark judge](abc-benchmark.md) (#878) — anti-Goodhart.
7
+ > Part of #877.
8
+
9
+ ## The thesis
10
+
11
+ AI UI is generic because UI generation has **no feedback loop**: the model writes markup blind, never sees the rendered
12
+ pixels, and defends whatever it produced. Every other lever in the harness — the spec (#881), the effects doctrine
13
+ (#885), divergent generation (#883) — still operates *before* a pixel exists. This loop is the one stage that closes
14
+ the loop: render the page, *look at it*, and feed a correction back in. It is the missing reward signal.
15
+
16
+ ## Why a loop, not a one-shot check
17
+
18
+ A single post-hoc lint pass catches deterministic tells but cannot see whether the *rendered* page has hierarchy,
19
+ whether an effect served the content, or whether the composition is generic. Those are perceptual judgements that only
20
+ exist once pixels are on screen, and fixing one often surfaces another. So the correction must **iterate**: critique →
21
+ fix → re-render → re-critique, until the page passes or the budget is spent.
22
+
23
+ ## Iteration protocol
24
+
25
+ 1. **Deterministic floor first.** Run the linter (#884) before every render. Never spend a render on a page that fails
26
+ a hard rule. This keeps the expensive perceptual step off pages a cheap check already rejects.
27
+ 2. **Render** at desktop + mobile, using the same Playwright protocol as the benchmark (#878/#875).
28
+ 3. **Vision-critique the pixels** against the perceptual negatives the linter declares out of scope — N3 (centered / no
29
+ asymmetry), N8 (stock / generic 3D), N9 (glassmorphism overuse), N10 (no hierarchy), N11 (abstract copy / no product
30
+ shown), N12 (decorative effects that serve nothing) — plus the spec's own acceptance and the `effects_plan`
31
+ justifications (#885).
32
+ 4. **Emit a `FORGE:CRITIQUE`** per iteration so the improvement trajectory is auditable.
33
+ 5. **Iterate** until no perceptual findings remain (PASS) or `max_iters` is reached (BUDGET-EXHAUSTED with residuals).
34
+
35
+ ## Division of labour (no duplication)
36
+
37
+ | Stage | Owns | Nature |
38
+ |---|---|---|
39
+ | Linter (#884) | deterministic negatives (N1, N2, N4-structural, contrast floor, off-scale spacing) | cheap, hard gate, runs first |
40
+ | This critic (#882) | perceptual negatives N3, N8–N12 + spec acceptance | iterative, perceptual |
41
+ | Perf gate (#875) | `effects_plan.budget` (LCP/CLS/INP/JS-kb) | hard, objective |
42
+
43
+ The critic never re-checks a deterministic rule; the linter never judges perception. Each negative has exactly one
44
+ owner.
45
+
46
+ ## Anti-Goodhart independence
47
+
48
+ The loop's critic is **not** the ABC benchmark judge (#878) and **not** the divergent-generation taste-judge (#883).
49
+ The benchmark judge is the independent scoreboard that tells us whether the harness improved; optimizing the loop
50
+ against it would be training on the test set. The taste-judge scores pre-render candidate directions. Three judges,
51
+ three jobs — the separation is what keeps the reward signal honest.
52
+
53
+ ## Success measure
54
+
55
+ The loop is working when a page **measurably improves across iterations**, and when — on the seed set, scored by the
56
+ independent benchmark (#878) against the #879 baseline — *A-with-loop* > *A-without-loop* > *raw B*. The loop produces
57
+ the improvement; the benchmark, which the loop never sees, confirms it.
@@ -17,6 +17,27 @@ This tutorial gets you from zero to your first autonomous pipeline run in under
17
17
  - [Claude Code](https://docs.anthropic.com/en/docs/claude-code) installed and authenticated
18
18
  - Git and GitHub CLI (`gh`) installed and authenticated (`gh auth login`)
19
19
  - Node.js 18 or higher
20
+ - [`yq`](https://github.com/mikefarah/yq) — YAML parser used by all pipeline commands to read `forge.yaml`
21
+
22
+ **Install `yq`:**
23
+
24
+ ```bash
25
+ # macOS
26
+ brew install yq
27
+
28
+ # Ubuntu / Debian
29
+ sudo apt-get install yq
30
+ # or: sudo snap install yq
31
+
32
+ # Windows
33
+ winget install mikefarah.yq
34
+ # or: choco install yq
35
+
36
+ # Any platform (via Go)
37
+ go install github.com/mikefarah/yq/v4@latest
38
+ ```
39
+
40
+ > **Note:** Without `yq`, pipeline commands that read `forge.yaml` (including `/work-on`, `/scope`, `/orchestrate`, and most others) will fall back to defaults or skip config-driven steps silently.
20
41
 
21
42
  ---
22
43
 
@@ -0,0 +1,397 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios
3
+ SPDX-License-Identifier: AGPL-3.0-or-later
4
+ -->
5
+
6
+ # Spec Knowledge Graph — Schema & Builder
7
+
8
+ The **spec knowledge graph** is a queryable, zero-dependency self-map of
9
+ ForgeDock's own command specs. It lets agents load only task-relevant specs and
10
+ run impact analysis on the pipeline's information flow ("which command writes
11
+ `FORGE:CONTRACT`? which commands read it?").
12
+
13
+ It is a native replication of code-graph tooling (codegraph,
14
+ codebase-memory-mcp) adapted to **prose specs** — no tree-sitter, SQLite, or MCP
15
+ dependency. The builder is a single Node `.mjs` artifact using Node built-ins
16
+ only (`fs`, `path`, `url`).
17
+
18
+ ## Building the graph
19
+
20
+ ```bash
21
+ node scripts/build-spec-graph.mjs # writes .forgedock/graph/spec-graph.json
22
+ node scripts/build-spec-graph.mjs --stdout # print to stdout (no file write)
23
+ node scripts/build-spec-graph.mjs --help # full options
24
+ ```
25
+
26
+ The builder is **idempotent**: all node/edge arrays are sorted by a stable
27
+ composite key and JSON is emitted with recursively sorted object keys, so
28
+ re-runs produce byte-identical output. It runs in well under 1s on the current
29
+ spec set (~90 nodes / ~120 edges, ~60ms).
30
+
31
+ Output is written to `.forgedock/graph/spec-graph.json` and is **gitignored by
32
+ default** (see `.gitignore`). This mirrors how per-repo adaptive scripts are
33
+ treated — opt in to commit the graph if you want it versioned.
34
+
35
+ ## Output document
36
+
37
+ A single JSON document:
38
+
39
+ ```jsonc
40
+ {
41
+ "graph": {
42
+ "schemaVersion": 1,
43
+ "generator": "build-spec-graph.mjs",
44
+ "root": ".",
45
+ "builtFromHash": "716dae74…", // sha256 of the scanned spec corpus
46
+ "stats": {
47
+ "nodes": 92,
48
+ "edges": 120,
49
+ "nodesByType": { "annotation": 30, "command": 35, "...": 0 },
50
+ "edgesByType": { "WRITES": 28, "READS": 45, "...": 0 }
51
+ },
52
+ "nodes": [ /* sorted by id */ ],
53
+ "edges": [ /* sorted by (from, type, to) */ ]
54
+ }
55
+ }
56
+ ```
57
+
58
+ It is `jq`-queryable. Examples:
59
+
60
+ ```bash
61
+ GRAPH=.forgedock/graph/spec-graph.json
62
+
63
+ # What does work-on write?
64
+ jq -r '.graph.edges[] | select(.from=="cmd:work-on" and .type=="WRITES") | .to' "$GRAPH"
65
+
66
+ # Who reads FORGE:CONTRACT?
67
+ jq -r '.graph.edges[] | select(.type=="READS" and .to=="ann:FORGE:CONTRACT") | .from' "$GRAPH"
68
+
69
+ # Which commands transition workflow:merged?
70
+ jq -r '.graph.edges[] | select(.type=="TRANSITIONS" and .to=="label:workflow:merged") | .from' "$GRAPH"
71
+ ```
72
+
73
+ ### `builtFromHash` — input fingerprint
74
+
75
+ `graph.builtFromHash` is a **sha256 fingerprint of the input corpus** the graph
76
+ was built from: every `commands/**/*.md`, `scripts/*.sh`, and `devdocs/**/*.md`
77
+ file's repo-relative path + content, hashed in sorted order. It is **purely
78
+ input-derived** — no timestamps, mtimes, or absolute paths — so identical inputs
79
+ always produce the same hash and the emitted graph stays byte-identical
80
+ (idempotency is preserved).
81
+
82
+ It is the basis for **staleness detection without a daemon**. Recompute it
83
+ cheaply at any time with the builder's `--hash` mode (no full graph build):
84
+
85
+ ```bash
86
+ node scripts/build-spec-graph.mjs --hash # prints just the sha256, exits
87
+ ```
88
+
89
+ If `node scripts/build-spec-graph.mjs --hash` differs from the persisted
90
+ graph's `builtFromHash`, the spec corpus changed since the graph was built — the
91
+ graph is stale. `graph-query.sh` performs exactly this comparison on every query
92
+ (see below).
93
+
94
+ ## Querying the graph (`graph-query.sh`)
95
+
96
+ `scripts/graph-query.sh` is the consumer-facing query API over the graph, so
97
+ agents (and humans) don't hand-write `jq`. It is a **universal-tier**,
98
+ **read-only** script: `bash` + `jq`, with `node` used to recompute the staleness
99
+ fingerprint and to auto-build/rebuild the graph (to a temp file) when the
100
+ persisted JSON is absent or stale. No committed graph is required.
101
+
102
+ ```bash
103
+ graph-query.sh <subcommand> <arg> [--human] [--graph <path>] [--strict-stale] [--no-stale-check]
104
+ ```
105
+
106
+ | Subcommand | Returns |
107
+ |-----------------------|-------------------------------------------------------------------|
108
+ | `readers <annotation>`| commands/specs that **READ** the annotation |
109
+ | `writers <annotation>`| commands/specs that **WRITE** (post) the annotation |
110
+ | `impact <node>` | blast radius — everything transitively affected if `<node>` changes |
111
+ | `deps <command>` | a command's full input/output set, grouped by edge type |
112
+ | `load-set <command>` | minimal spec set to read for a command — the command plus its transitively reachable sub-phases (`CONTAINS`) and required devdocs (`REQUIRES`), as a flat list of repo-relative file paths |
113
+ | `search <term>` | substring lookup over node ids / names / paths |
114
+
115
+ Arguments are normalized, so all of these resolve to the same node:
116
+
117
+ ```bash
118
+ graph-query.sh readers CONTRACT # == readers FORGE:CONTRACT == readers ann:FORGE:CONTRACT
119
+ graph-query.sh deps work-on # == deps cmd:work-on
120
+ graph-query.sh deps work-on:build:implement # sub-phases use the Skill() `:`-delimited form
121
+ graph-query.sh load-set work-on # == load-set cmd:work-on
122
+ graph-query.sh impact classify-lane.sh # == impact script:classify-lane.sh
123
+ graph-query.sh impact workflow:merged # == impact label:workflow:merged
124
+ ```
125
+
126
+ `impact` is **transitive reverse-reachability**: it seeds at `<node>` and
127
+ collects every command that (directly or transitively) `READS`/`WRITES`/
128
+ `INVOKES`/`REQUIRES`/`CONTAINS` it, walking up parent commands via `CONTAINS`.
129
+ So `impact FORGE:CONTRACT` returns not only its direct readers/writers but also
130
+ the parent commands that contain them (e.g. `cmd:orchestrate`, `cmd:milestone`).
131
+
132
+ `load-set` is the **forward inverse of `impact`**: it seeds at `<command>` and
133
+ walks `CONTAINS` (sub-phases) and `REQUIRES` (devdocs) edges in the *forward*
134
+ direction to a fixpoint, then maps the reachable nodes to their repo-relative
135
+ file `path`s. The result is the **minimal spec set** an agent must read to run
136
+ that command — the command itself plus only the sub-phases and devdocs actually
137
+ reachable from it, never the full ~27-command corpus. It is the resolver that
138
+ powers selective spec loading (Phase 0 of `work-on`). `READS`/`WRITES`/
139
+ `TRANSITIONS`/`INVOKES` edges are *not* followed — annotations and labels have
140
+ no spec file, and scripts are resolved separately at invoke time. The walk shares
141
+ `impact`'s cycle-safe termination, so a self-referential `CONTAINS` edge cannot
142
+ loop forever.
143
+
144
+ ### Output
145
+
146
+ Default output is **compact, agent-consumable JSON** — a JSON array for
147
+ `readers`/`writers`/`impact`/`search`, a JSON object keyed by edge type for
148
+ `deps`, and a sorted, de-duplicated JSON array of file paths for `load-set`.
149
+ Pass `--human` for an aligned table instead.
150
+
151
+ ```bash
152
+ $ graph-query.sh writers FORGE:CONTRACT
153
+ ["cmd:work-on","cmd:work-on:build"]
154
+
155
+ $ graph-query.sh deps work-on:build:implement
156
+ {"READS":["ann:FORGE:ARCHITECT","ann:FORGE:CONTEXT","ann:FORGE:CONTRACT","ann:FORGE:INVESTIGATOR"],"WRITES":["ann:FORGE:BUILDER"]}
157
+
158
+ $ graph-query.sh load-set work-on
159
+ ["commands/review-pr.md","commands/work-on.md","commands/work-on/build.md","commands/work-on/build/architect.md","commands/work-on/build/context.md","commands/work-on/build/implement.md","commands/work-on/build/validate.md","commands/work-on/close.md","commands/work-on/decompose.md","commands/work-on/investigate.md","commands/work-on/review.md"]
160
+
161
+ $ graph-query.sh writers FORGE:CONTRACT --human
162
+ Specs that WRITE ann:FORGE:CONTRACT:
163
+ cmd:work-on
164
+ cmd:work-on:build
165
+ ```
166
+
167
+ ### Flags & behavior
168
+
169
+ - `--graph <path>` — query a specific graph JSON (default:
170
+ `.forgedock/graph/spec-graph.json`). When the default path is absent (it is
171
+ gitignored), the graph is **auto-built on the fly** via `build-spec-graph.mjs`
172
+ into a temp file (cleaned up on exit).
173
+ - `--human` — render an aligned table instead of compact JSON.
174
+ - `--strict-stale` — do **not** auto-rebuild a stale persisted graph; error
175
+ (exit 1) instead. Useful in CI to assert a committed graph is fresh.
176
+ - `--no-stale-check` — skip the staleness check entirely and query the persisted
177
+ graph as-is, even if the spec corpus has changed.
178
+ - Exit codes: `0` on success, `1` on bad args, missing `jq`/`node`, an unknown
179
+ node (the error suggests `graph-query.sh search <term>`), or a stale graph
180
+ under `--strict-stale`.
181
+
182
+ ### Staleness detection (no daemon)
183
+
184
+ ForgeDock targets non-coders, so there is **no file-watcher daemon**. Staleness
185
+ is detected **pull-based, on query**:
186
+
187
+ 1. When a query uses a **persisted** graph (the default
188
+ `.forgedock/graph/spec-graph.json`, or an explicit `--graph <path>`),
189
+ `graph-query.sh` recomputes the current corpus fingerprint via
190
+ `build-spec-graph.mjs --hash` and compares it to the graph's stored
191
+ `builtFromHash`.
192
+ 2. **Match** → the persisted graph is used as-is. The no-change path is a single
193
+ cheap hash compare — no rebuild, no perceptible latency, no background
194
+ process.
195
+ 3. **Mismatch** (or a graph predating `builtFromHash`) → the graph is stale. By
196
+ default a `stale-graph` banner is printed to **stderr** and the graph is
197
+ transparently **rebuilt** for this query; **stdout stays pure JSON**. With
198
+ `--strict-stale` the query errors instead; with `--no-stale-check` the check
199
+ is skipped.
200
+ 4. When **no persisted graph exists** (the common case — the graph is
201
+ gitignored), `graph-query.sh` auto-builds a fresh graph to a temp file. A
202
+ freshly built graph is current by construction, so no staleness check runs.
203
+
204
+ This means editing any `commands/*.md` (or `scripts/*.sh` / `devdocs/**`) is
205
+ detected on the next query and the rebuild produces a fresh hash — with no daemon
206
+ and no cost on the no-change path.
207
+
208
+ ### Optional: rebuild-on-commit hook
209
+
210
+ For contributors who choose to keep a **committed** graph in sync, an **opt-in**
211
+ git pre-commit hook ships at `.githooks/pre-commit`. It is never auto-installed.
212
+ Enable it with:
213
+
214
+ ```bash
215
+ git config core.hooksPath .githooks
216
+ ```
217
+
218
+ When enabled, the hook rebuilds `.forgedock/graph/spec-graph.json` and stages it
219
+ **only if** (a) a persisted graph already exists and (b) the commit stages a file
220
+ under `commands/`, `scripts/`, or `devdocs/`. If no persisted graph exists (the
221
+ default), the hook is a no-op. It never blocks a commit on rebuild failure.
222
+
223
+ ## Validating the graph (`validate-spec-graph.sh`)
224
+
225
+ `scripts/validate-spec-graph.sh` is the **self-consistency validator** over the
226
+ graph. It catches drift that is invisible in the ~1.2 MB prose corpus and is
227
+ wired into `quality-gate` (the `FORGE_GRAPH` domain) so it runs whenever
228
+ `commands/*.md` or `scripts/*` files change. Like `graph-query.sh`, it is a
229
+ **universal-tier**, **read-only** script (`bash` + `jq`, `node` only for the
230
+ on-the-fly auto-build) that auto-builds the graph when the gitignored JSON is
231
+ absent — no committed graph is required.
232
+
233
+ ```bash
234
+ validate-spec-graph.sh [--soft] [--graph <path>] [--root <dir>] [--quiet] [--json]
235
+ ```
236
+
237
+ It performs three checks, partitioned by severity:
238
+
239
+ | Check | Severity | Detects |
240
+ |---------------------------|--------------|-------------------------------------------------------------------------|
241
+ | **Orphan annotations** | SOFT (warn) | a `FORGE:*` marker WRITTEN but never READ, or READ but never WRITTEN |
242
+ | **Dangling cross-refs** | HARD (fail) | a `Skill(skill="X")` whose target is no real command/sub-phase node |
243
+ | **Broken transitions** | HARD (fail) | a `workflow:*` label set by **no** command (no `--add-label` edge) |
244
+
245
+ Inline doc-scaffolding markers (`FORGE:DISPATCHER`, `FORGE:PHASE_COMPLETE`,
246
+ `FORGE:TYPE`, …) legitimately have neither a WRITES nor a READS edge — they
247
+ annotate the spec prose itself and are never posted via `gh ... comment`. They
248
+ are reported as **INFO** (via a curated allowlist in the script), never as
249
+ orphans. A non-allowlisted isolated marker is surfaced as SOFT so new
250
+ scaffolding gets triaged.
251
+
252
+ **Why dangling refs are scanned from prose, not edges**: the builder *drops*
253
+ unresolved `Skill()`/script/devdoc references (it only emits an edge when the
254
+ target resolves to a real node — see `if (nodes.has(targetId))` in
255
+ `build-spec-graph.mjs`). So a dangling reference never appears as a graph edge.
256
+ The validator therefore scans `commands/**/*.md` for `Skill(skill="X")`
257
+ directly and cross-checks each `X` against the command/sub-phase node set. Only
258
+ the structured `Skill()` form is checked — loose `.sh`/filename mentions in
259
+ prose are illustrative and are intentionally not flagged.
260
+
261
+ ### Flags & exit codes
262
+
263
+ - `--soft` — warn-only mode: HARD findings are still reported, but the exit code
264
+ is forced to `0`. Used when first wiring into CI so the documented baseline
265
+ orphans/refs do not break the build before triage. `quality-gate` invokes the
266
+ validator with `--soft` and re-surfaces any `[HARD]` line as a HIGH finding.
267
+ - `--graph <path>` — validate a specific graph JSON (default
268
+ `.forgedock/graph/spec-graph.json`, auto-built when absent).
269
+ - `--root <dir>` — repo root to scan for `Skill()` refs and to auto-build from.
270
+ - `--quiet` — print only the summary line. `--json` — emit findings as a single
271
+ JSON object (`{summary:{hard,soft,info}, findings:[{severity,class,message}]}`).
272
+ - Exit codes: `0` when there are no HARD findings (or `--soft`); `1` on a HARD
273
+ inconsistency, bad args, or a missing dependency. Output is deterministic
274
+ (findings sorted by severity → class → message), so re-runs are byte-stable.
275
+
276
+ ## Node types
277
+
278
+ | `type` | `id` shape | Source |
279
+ |--------------|----------------------------------|-----------------------------------------|
280
+ | `command` | `cmd:<name>` | top-level `commands/<name>.md` |
281
+ | `sub-phase` | `cmd:<a>:<b>[:<c>]` | nested `commands/<a>/<b>[/<c>].md` |
282
+ | `annotation` | `ann:FORGE:<NAME>` | distinct `FORGE:*` HTML-comment marker |
283
+ | `label` | `label:workflow:<name>` | distinct `workflow:*` label |
284
+ | `script` | `script:<file>.sh` | `scripts/*.sh` file on disk |
285
+ | `devdoc` | `devdoc:devdocs/<path>.md` | `devdocs/**/*.md` reference doc |
286
+
287
+ Sub-phase names use the same `:`-delimited convention as `Skill(skill="...")`
288
+ targets, so `commands/work-on/build/implement.md` → `cmd:work-on:build:implement`.
289
+
290
+ Every node carries `id`, `type`, `name`; file-backed nodes also carry `path`
291
+ (repo-relative).
292
+
293
+ ## Edge types
294
+
295
+ All edges originate from a `command` or `sub-phase` node.
296
+
297
+ | `type` | Target | Meaning |
298
+ |---------------|-------------|------------------------------------------------------|
299
+ | `WRITES` | annotation | command **posts** the FORGE annotation |
300
+ | `READS` | annotation | command **consumes** the FORGE annotation |
301
+ | `TRANSITIONS` | label | command **sets** the workflow label |
302
+ | `CONTAINS` | sub-phase | parent → nested phase (directory) or `Skill()` invoke |
303
+ | `INVOKES` | script | command runs a `scripts/*.sh` file |
304
+ | `REQUIRES` | devdoc | command **must read** a devdoc (authority: required) |
305
+
306
+ Each edge carries `from`, `type`, `to`, and an `evidence` object with the
307
+ source `file`/`line` (and `via`/`authority` where relevant).
308
+
309
+ ## Extraction heuristics
310
+
311
+ The builder regex-scans each `commands/**/*.md` line by line:
312
+
313
+ - **WRITES** — line matches `gh (issue|pr) comment ... --body` and a
314
+ `<!-- FORGE:X -->` marker appears in the body portion (same line or the
315
+ immediately following line, since `--body` blocks often wrap). This captures
316
+ the *posting* form only.
317
+ - **READS** — line matches `contains("FORGE:X")` (the jq-select consume form) or
318
+ prose `read[s]/re-read the FORGE:X` (the narrative consume form).
319
+ - **TRANSITIONS** — line matches `--add-label "...workflow:X..."`. A single
320
+ `--add-label` may carry a comma-separated list; each label becomes an edge.
321
+ - **CONTAINS** — two sources: (a) directory nesting (`cmd:work-on` →
322
+ `cmd:work-on:build` → `cmd:work-on:build:implement`), and (b)
323
+ `Skill(skill="X")` invocations where `X` resolves to a known command/sub-phase
324
+ node.
325
+ - **INVOKES** — any `*.sh` token in the spec that matches a real `scripts/*.sh`
326
+ filename on disk. Tokens that don't correspond to a real script are ignored.
327
+ - **REQUIRES** — a line that references a `devdocs/*.md` path **and** carries a
328
+ requirement verb (`Read` / `required` / `must` / `before`), with
329
+ `authority: "required"`.
330
+
331
+ ### WRITES vs READS — false-positive handling
332
+
333
+ The hardest distinction is **WRITES vs READS** of the same annotation. The
334
+ distinguishing signal is the surrounding command, not the marker itself:
335
+
336
+ - A spec that says *post* `<!-- FORGE:BUILDER -->` (via `gh ... comment --body`)
337
+ **writes** it.
338
+ - A spec that says *read* the `FORGE:CONTRACT` (via `contains("FORGE:CONTRACT")`
339
+ or "read the FORGE:CONTRACT") **reads** it.
340
+
341
+ Documentation scaffolding markers — inline `<!-- FORGE:PHASE_COMPLETE -->` and
342
+ `<!-- FORGE:DISPATCHER -->` HTML comments used to annotate the spec prose itself
343
+ — are **not** counted as WRITES, because they never appear inside a
344
+ `gh ... comment --body` posting. They still get `annotation` nodes (they are
345
+ real markers in the corpus) but produce no spurious WRITES edges. This guard is
346
+ verified by the builder's self-check.
347
+
348
+ ## Self-check
349
+
350
+ On every run the builder asserts three acceptance spot-checks and exits non-zero
351
+ if any fails:
352
+
353
+ 1. `work-on` **WRITES** `FORGE:TRAJECTORY`.
354
+ 2. `review-pr` **READS** `FORGE:CONTRACT`.
355
+ 3. `builtFromHash` is present and is a 64-char sha256 hex digest.
356
+
357
+ ## Adding new node or edge types
358
+
359
+ Extend `commandNodeFromPath()` / the node-discovery passes for new node types,
360
+ and add a new heuristic block in the per-line loop of `build()` for new edge
361
+ types. Keep all output sorted to preserve idempotency, and add a self-check
362
+ assertion for any new invariant.
363
+
364
+ ## Open-core boundary
365
+
366
+ The graph is **open-core, CLI-side, local-only**. The builder, store, and query
367
+ tools (`build-spec-graph.mjs`, `graph-query.sh`, `validate-spec-graph.sh`) are
368
+ AGPL code in this repo; they run entirely on-disk and emit a structured JSON
369
+ artifact (`.forgedock/graph/spec-graph.json`, gitignored by default).
370
+
371
+ That JSON is the boundary. The Platform's L1 observability dashboard *renders*
372
+ the graph as a pipeline self-map (runs, timelines, stall detection) — that
373
+ visualization is the **commercial value-add**. The Platform consumes the emitted
374
+ JSON; it never imports, embeds, vendors, or links the AGPL builder/query code
375
+ (see `devdocs/project/architecture.md` → Boundary Rules). The CLI does not know
376
+ or care whether the Platform exists — emitting the JSON is the entire contract.
377
+
378
+ ## Optional context hook (deferred — off by default)
379
+
380
+ A future opt-in Claude Code `PreToolUse` hook can use the graph to inject
381
+ "relevant specs per graph" as context when an agent greps `commands/`, mirroring
382
+ codegraph's `PreToolUse` pattern. It would shell out to
383
+ `graph-query.sh load-set <command>` (or `readers`/`deps`) and surface the
384
+ task-relevant spec set instead of letting the agent load blindly.
385
+
386
+ **Decision: deferred — not shipped in this milestone.** It must be:
387
+
388
+ - **Opt-in, off by default** — gated behind an explicit setting (e.g. a
389
+ `hooks.spec_graph_context` flag in `forge.yaml` or the user's Claude Code
390
+ settings), so the graph imposes nothing on users who do not enable it.
391
+ - **Non-blocking** — a `PreToolUse` hook that only *adds* context and never
392
+ rejects a tool call; a missing or stale graph degrades to a no-op.
393
+ - **CLI-side** — the hook reads the local JSON via the AGPL query scripts; it
394
+ introduces no Platform dependency.
395
+
396
+ Shipping the wiring is deferred because no hook-infrastructure layer exists in
397
+ the repo yet; this section is the spec that a later issue implements against.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forgedock",
3
- "version": "1.0.16",
3
+ "version": "1.0.18",
4
4
  "description": "GitHub as a knowledge graph for AI agents. Autonomous dev pipeline for Claude Code.",
5
5
  "bin": {
6
6
  "forgedock": "bin/forgedock.mjs"
@@ -27,7 +27,7 @@
27
27
  "type": "git",
28
28
  "url": "git+https://github.com/RapierCraftStudios/ForgeDock.git"
29
29
  },
30
- "homepage": "https://github.com/RapierCraftStudios/ForgeDock",
30
+ "homepage": "https://forgedock.dev",
31
31
  "bugs": {
32
32
  "url": "https://github.com/RapierCraftStudios/ForgeDock/issues"
33
33
  },
@@ -52,7 +52,7 @@
52
52
  },
53
53
  "peerDependenciesMeta": {
54
54
  "@anthropic-ai/claude-code": {
55
- "optional": false
55
+ "optional": true
56
56
  }
57
57
  }
58
58
  }