forgedock 1.0.22 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/README.md +255 -211
  2. package/bin/batch-runner.mjs +339 -0
  3. package/bin/cinema.mjs +320 -0
  4. package/bin/demo.mjs +92 -7
  5. package/bin/engine/phases.mjs +168 -0
  6. package/bin/engine/projector.mjs +32 -0
  7. package/bin/engine/reconcile.mjs +11 -0
  8. package/bin/engine/runlog.mjs +79 -0
  9. package/bin/engine/state.mjs +32 -0
  10. package/bin/engine-cli.mjs +222 -0
  11. package/bin/engine.mjs +138 -0
  12. package/bin/forgedock.mjs +896 -1540
  13. package/bin/hooks/interactive-engine.mjs +509 -0
  14. package/bin/hooks/pre-tool-use.mjs +498 -0
  15. package/bin/hooks/subagent-stop-enforce.mjs +197 -0
  16. package/bin/journey.mjs +841 -0
  17. package/bin/registry.mjs +139 -0
  18. package/bin/report.mjs +447 -0
  19. package/bin/runner.mjs +136 -20
  20. package/bin/settings-hook.mjs +346 -0
  21. package/bin/tui.mjs +6 -3
  22. package/commands/adopt.md +4 -1
  23. package/commands/analytics.md +4 -1
  24. package/commands/audit-agents.md +110 -27
  25. package/commands/audit.md +20 -1
  26. package/commands/autopilot.md +84 -12
  27. package/commands/changelog.md +2 -1
  28. package/commands/ci-audit.md +2 -1
  29. package/commands/cleanup.md +161 -1
  30. package/commands/compat-audit.md +2 -1
  31. package/commands/deploy-info.md +63 -4
  32. package/commands/design-bench.md +6 -1
  33. package/commands/design-render-critique-loop.md +2 -1
  34. package/commands/design.md +1 -0
  35. package/commands/diagnose.md +2 -1
  36. package/commands/explain.md +2 -1
  37. package/commands/forge-stats.md +122 -2
  38. package/commands/forgedock-init.md +1 -1
  39. package/commands/geo-audit.md +3 -0
  40. package/commands/incident-response.md +2 -1
  41. package/commands/issue.md +24 -3
  42. package/commands/milestone.md +77 -12
  43. package/commands/optimize.md +2 -1
  44. package/commands/orchestrate/config.md +67 -0
  45. package/commands/orchestrate/phase-1-resolve.md +159 -0
  46. package/commands/orchestrate/phase-2-triage.md +126 -0
  47. package/commands/orchestrate/phase-2.5-synthesis.md +156 -0
  48. package/commands/orchestrate/phase-3-dependency.md +591 -0
  49. package/commands/orchestrate/phase-4-execution.md +962 -0
  50. package/commands/orchestrate/phase-5-cleanup.md +47 -0
  51. package/commands/orchestrate/phase-6-report.md +198 -0
  52. package/commands/orchestrate/safety.md +62 -0
  53. package/commands/orchestrate.md +25 -2027
  54. package/commands/pipeline-health/config.md +34 -0
  55. package/commands/pipeline-health/phase-1-context.md +180 -0
  56. package/commands/pipeline-health/phase-2-metrics.md +1729 -0
  57. package/commands/pipeline-health/phase-3-analyze.md +204 -0
  58. package/commands/pipeline-health/phase-4-proposals.md +117 -0
  59. package/commands/pipeline-health/phase-5-report.md +284 -0
  60. package/commands/pipeline-health/phase-6-summary.md +52 -0
  61. package/commands/pipeline-health.md +25 -2401
  62. package/commands/pipeline-resume.md +24 -1
  63. package/commands/pipeline-status.md +65 -0
  64. package/commands/qa-sweep.md +4 -1
  65. package/commands/quality-gate.md +326 -42
  66. package/commands/replay.md +2 -1
  67. package/commands/review-pr-agents/api.md +135 -0
  68. package/commands/review-pr-agents/auth.md +137 -0
  69. package/commands/review-pr-agents/billing.md +101 -0
  70. package/commands/review-pr-agents/concurrency.md +145 -0
  71. package/commands/review-pr-agents/database.md +103 -0
  72. package/commands/review-pr-agents/frontend.md +89 -0
  73. package/commands/review-pr-agents/infra.md +385 -0
  74. package/commands/review-pr-agents/protocols.md +185 -0
  75. package/commands/review-pr-agents/scraper.md +185 -0
  76. package/commands/review-pr-agents/security.md +406 -0
  77. package/commands/review-pr-agents.md +45 -1722
  78. package/commands/review-pr-staging.md +196 -40
  79. package/commands/review-pr.md +612 -56
  80. package/commands/rollback.md +2 -1
  81. package/commands/scope.md +2 -1
  82. package/commands/security-audit.md +2 -1
  83. package/commands/signal-planner.md +623 -0
  84. package/commands/sync-ecosystem.md +1 -0
  85. package/commands/test-gate.md +17 -1
  86. package/commands/validate.md +2 -1
  87. package/commands/work-on/build/architect.md +28 -0
  88. package/commands/work-on/build/context.md +2 -0
  89. package/commands/work-on/build/implement.md +19 -5
  90. package/commands/work-on/build/validate.md +62 -6
  91. package/commands/work-on/build.md +187 -30
  92. package/commands/work-on/close.md +132 -5
  93. package/commands/work-on/decompose.md +19 -1
  94. package/commands/work-on/investigate.md +117 -2
  95. package/commands/work-on/review.md +48 -4
  96. package/commands/work-on-monolithic.md +8 -2
  97. package/commands/work-on.md +247 -7
  98. package/docs/BRAND.md +16 -5
  99. package/docs/CI.md +2 -2
  100. package/docs/CODEX.md +36 -1
  101. package/docs/CONFIG.md +44 -42
  102. package/docs/FORGE-PROTOCOL.md +15 -1064
  103. package/docs/anthropic-devrel-outreach.md +2 -2
  104. package/docs/articles/command-decomposition-token-savings.md +214 -0
  105. package/docs/articles/model-release-playbook.md +131 -0
  106. package/docs/articles/per-repo-adaptive-scripts-token-savings.md +27 -25
  107. package/docs/claude-breakpoints.json +75 -5
  108. package/docs/design/abc-benchmark.md +1 -1
  109. package/docs/design/design-spec-schema.md +1 -1
  110. package/docs/design/divergent-generation.md +1 -1
  111. package/docs/design/eval-gate-runbook.md +178 -0
  112. package/docs/design/show-hn-draft.md +26 -1
  113. package/docs/design/token-cost-baseline-1298.md +59 -0
  114. package/docs/eval/README.md +117 -0
  115. package/docs/eval/corpus/seed-v1.json +6 -0
  116. package/docs/eval/published/claude-sonnet-4-6-2026-07-04.json +33 -0
  117. package/docs/site/annotations-explained.md +24 -0
  118. package/docs/site/command-learning-path.md +51 -14
  119. package/docs/site/command-reference.md +339 -113
  120. package/docs/site/for-companies.md +95 -0
  121. package/docs/site/forge-annotation-protocol.md +12 -256
  122. package/docs/site/getting-started.md +58 -12
  123. package/docs/site/github-is-the-memory.md +113 -0
  124. package/docs/site/how-it-works.md +3 -13
  125. package/docs/site/index.md +10 -2
  126. package/docs/spec/eval-run-result.md +147 -0
  127. package/docs/spec/forge-protocol-v1.md +441 -18
  128. package/docs/spec/label-state-machine.md +55 -0
  129. package/docs/spec/review-protocol.md +158 -0
  130. package/docs/spec-graph-schema.md +28 -0
  131. package/docs/superpowers/plans/2026-07-03-cinematic-onboarding-tui.md +2339 -0
  132. package/docs/superpowers/plans/2026-07-03-durable-execution-engine.md +1223 -0
  133. package/docs/superpowers/specs/2026-07-03-cinematic-onboarding-tui-design.md +157 -0
  134. package/docs/superpowers/specs/2026-07-03-durable-execution-engine-design.md +194 -0
  135. package/docs/superpowers/specs/2026-07-04-economic-self-governance-design.md +181 -0
  136. package/docs/superpowers/specs/2026-07-04-provenance-trail-design.md +189 -0
  137. package/examples/forgedock-demo/README.md +41 -22
  138. package/examples/forgedock-demo/issues/06-bug-tags-type-validation.md +43 -0
  139. package/examples/forgedock-demo/issues/07-bug-negative-offset-pagination.md +42 -0
  140. package/examples/forgedock-demo/issues/08-bug-patch-mass-assignment.md +49 -0
  141. package/examples/forgedock-demo/issues/09-bug-count-injection.md +47 -0
  142. package/examples/forgedock-demo/issues/10-feature-sort-param.md +39 -0
  143. package/examples/forgedock-demo/issues/11-feature-patch-tags.md +37 -0
  144. package/examples/forgedock-demo/issues/12-feature-bulk-create.md +42 -0
  145. package/examples/forgedock-demo/issues/13-feature-archive-notes.md +46 -0
  146. package/examples/forgedock-demo/issues/14-refactor-extract-title-validation.md +41 -0
  147. package/examples/forgedock-demo/issues/15-refactor-protected-route-flag.md +43 -0
  148. package/examples/forgedock-demo/issues/16-refactor-extract-store.md +45 -0
  149. package/examples/forgedock-demo/issues/17-perf-tags-dedup-set.md +35 -0
  150. package/examples/forgedock-demo/issues/18-perf-remove-redundant-sort.md +37 -0
  151. package/examples/forgedock-demo/issues/19-docs-api-reference-full.md +53 -0
  152. package/examples/forgedock-demo/issues/20-docs-intentional-flaw-convention.md +46 -0
  153. package/examples/forgedock-demo/scripts/smoke.js +18 -1
  154. package/examples/forgedock-demo/src/db.js +36 -11
  155. package/examples/forgedock-demo/src/routes/notes.js +106 -9
  156. package/examples/forgedock-demo/src/server.js +4 -1
  157. package/package.json +12 -4
  158. package/packages/protocol/LICENSE +21 -0
  159. package/packages/protocol/src/cli.js +135 -0
  160. package/packages/protocol/src/emit.js +146 -0
  161. package/packages/protocol/src/index.js +43 -0
  162. package/packages/protocol/src/parse.js +229 -0
  163. package/packages/protocol/src/types.js +171 -0
  164. package/packages/protocol/src/validate.js +122 -0
  165. package/scripts/bench-topology-cost.mjs +372 -0
  166. package/scripts/check-command-docs-drift.sh +198 -0
  167. package/scripts/check-protocol-restatements.sh +176 -0
  168. package/scripts/check-registry/README.md +72 -0
  169. package/scripts/check-registry/manifest.json +6 -0
  170. package/scripts/code-index.sh +454 -0
  171. package/scripts/conformance-check.mjs +168 -0
  172. package/scripts/doctor-pipeline-state.sh +484 -0
  173. package/scripts/eval-gate-baseline.json +17 -0
  174. package/scripts/eval-gate-scorecard.mjs +242 -0
  175. package/scripts/eval-scorecard-aggregator.mjs +252 -0
  176. package/scripts/flaky-quarantine.sh +241 -0
  177. package/scripts/forge-annotation.sh +567 -0
  178. package/scripts/issue-dedup.sh +236 -0
  179. package/scripts/validate-annotation-node.mjs +233 -0
  180. package/scripts/verify-env-vars.sh +18 -2
  181. package/scripts/verify-host-headers.sh +4 -4
  182. package/scripts/verify-route-registration.sh +2 -2
  183. package/scripts/worktree-lifecycle.sh +202 -0
  184. package/templates/devdocs/agent/using-forgedock.md +14 -0
@@ -0,0 +1,41 @@
1
+ ---
2
+ title: "refactor(notes): extract title validation into a shared validate.js module"
3
+ labels: ["refactor", "priority:P3"]
4
+ difficulty: "easy"
5
+ ---
6
+
7
+ ## Problem
8
+
9
+ `createNote()` in `src/routes/notes.js` has an inline `if (!title)` guard. When
10
+ the bulk-create endpoint (issue #12) is added, the same check will need to be
11
+ duplicated. Any future handler that creates or modifies a note title will face
12
+ the same duplication.
13
+
14
+ This is a pure refactor — no behavior change, no new validation rules.
15
+
16
+ ## Proposed Solution
17
+
18
+ Extract the title-presence check into a new `src/validate.js` module that
19
+ exports a single function: `validateNote({ title })`. The function throws a
20
+ `400`-status error if `title` is falsy, identical to the current inline guard.
21
+ `createNote()` (and, if issue #12 is already merged, `bulkCreateNotes()`) should
22
+ call `validateNote()` instead of the inline check.
23
+
24
+ ## Affected Files
25
+
26
+ 1. `src/validate.js` (NEW) — exports `validateNote({ title })`.
27
+ 2. `src/routes/notes.js` — replace the inline `if (!title)` block with
28
+ `validateNote({ title })` imported from `../validate`.
29
+
30
+ ## Acceptance Criteria
31
+
32
+ - [ ] `POST /notes` without a `title` still returns `400` with the same error
33
+ message as before.
34
+ - [ ] `src/routes/notes.js` no longer contains a bare `if (!title)` guard —
35
+ that logic lives only in `src/validate.js`.
36
+ - [ ] `npm run smoke` still passes.
37
+ - [ ] No behavior change in any endpoint.
38
+
39
+ > A zero-behavior-change refactor. The review agents should confirm that the
40
+ > 400 path, the error message, and all other responses are byte-for-byte
41
+ > identical to the pre-refactor baseline.
@@ -0,0 +1,43 @@
1
+ ---
2
+ title: "refactor(server): centralize auth enforcement with a protected flag in the route table"
3
+ labels: ["refactor", "priority:P3"]
4
+ difficulty: "medium"
5
+ ---
6
+
7
+ ## Problem
8
+
9
+ Authorization (`requireToken`) is enforced by each route handler individually.
10
+ As routes grow, there is no single place to see which routes are protected — a
11
+ reviewer must read every handler. Missing a `requireToken` call in a new handler
12
+ is exactly how issue #1's bug was introduced.
13
+
14
+ ## Proposed Solution
15
+
16
+ Add a boolean `protected` flag as the fourth element of each route-table entry
17
+ in `src/server.js`. The server's request dispatch loop should call
18
+ `requireToken(req)` centrally, before the handler is invoked, whenever the flag
19
+ is `true`. Handlers that currently call `requireToken()` themselves should have
20
+ that call removed (it is now redundant).
21
+
22
+ This is a pure refactor — every endpoint's authorization behavior must remain
23
+ identical.
24
+
25
+ ## Affected Files
26
+
27
+ 1. `src/server.js` — add `protected: true/false` to each TABLE entry; call
28
+ `requireToken(req)` in the dispatch loop when the flag is set.
29
+ 2. `src/routes/notes.js` — remove all `requireToken(req)` calls from handlers
30
+ that will be protected by the table flag instead.
31
+
32
+ ## Acceptance Criteria
33
+
34
+ - [ ] `POST /notes` without a token still returns `401`.
35
+ - [ ] `PATCH /notes/:id` without a token still returns `401`.
36
+ - [ ] `DELETE /notes/:id` still returns its current response (intentionally
37
+ unprotected in the baseline — issue #1 is not yet fixed).
38
+ - [ ] No `requireToken(req)` call remains inside any route handler.
39
+ - [ ] `npm run smoke` still passes.
40
+ - [ ] No behavior change in any endpoint.
41
+
42
+ > This refactor makes the authorization surface visible at a glance —
43
+ > a pattern that directly prevents the class of bug introduced in issue #1.
@@ -0,0 +1,45 @@
1
+ ---
2
+ title: "refactor(db): extract the generic array-store machinery into store.js"
3
+ labels: ["refactor", "priority:P3"]
4
+ difficulty: "hard"
5
+ ---
6
+
7
+ ## Problem
8
+
9
+ `src/db.js` conflates two things: the generic in-memory array/index operations
10
+ (`all`, `findById`, `insert`, `remove`, `reset`) and the Notes-specific seed
11
+ data and field defaults. If a second resource (e.g. users) were added, all of
12
+ that generic machinery would need to be duplicated.
13
+
14
+ This is a pure refactor — no behavior change, no new features.
15
+
16
+ ## Proposed Solution
17
+
18
+ Extract the generic store machinery into `src/store.js`, exporting a factory
19
+ function `createStore(seed, defaults)`. The returned object exposes:
20
+ `all`, `findById`, `insert`, `remove`, `reset`.
21
+
22
+ `src/db.js` becomes a thin wrapper: it calls `createStore(SEED, { secret:
23
+ false, tags: [], archived: false })` and re-exports the resulting methods plus
24
+ `query` and `bubbleSortById` (which remain in `db.js` as Notes-specific logic).
25
+
26
+ The public API visible to `routes/notes.js` must be byte-for-byte compatible.
27
+
28
+ ## Affected Files
29
+
30
+ 1. `src/store.js` (NEW) — generic `createStore(seed, defaults)` factory.
31
+ 2. `src/db.js` — delegates `all`/`findById`/`insert`/`remove`/`reset` to the
32
+ store instance; keeps `query`/`bubbleSortById`.
33
+
34
+ ## Acceptance Criteria
35
+
36
+ - [ ] `db.all()`, `db.findById()`, `db.insert()`, `db.remove()`, `db.reset()`
37
+ all behave identically to today.
38
+ - [ ] `db.query()` and `db.bubbleSortById()` are unchanged.
39
+ - [ ] No code in `src/routes/notes.js` or `src/server.js` changes.
40
+ - [ ] `npm run smoke` still passes.
41
+ - [ ] No behavior change in any endpoint.
42
+
43
+ > A structural refactor that introduces an abstraction boundary — the review
44
+ > agents should verify that the public interface is preserved exactly and that
45
+ > no observable behavior changes.
@@ -0,0 +1,35 @@
1
+ ---
2
+ title: "perf(notes): replace O(n²) tag de-duplication in listTags with a Set"
3
+ labels: ["performance", "priority:P3"]
4
+ difficulty: "easy"
5
+ ---
6
+
7
+ ## Problem
8
+
9
+ `GET /notes/tags` de-duplicates tags using a nested loop: for every tag in every
10
+ note it scans the entire `unique` array to check for a duplicate. This is O(n²)
11
+ on the total tag count. With a large corpus of notes and many tags, this becomes
12
+ slow.
13
+
14
+ The fix is a one-line change to use a `Set` for deduplication instead.
15
+
16
+ ## Investigation Hint
17
+
18
+ Before optimizing, confirm where the nested loop is and calculate the actual
19
+ time complexity. Verify that the `Set`-based replacement produces the same
20
+ output (same tags, same order — first-seen wins, matching the current
21
+ behavior).
22
+
23
+ ## Affected Files
24
+
25
+ 1. `src/routes/notes.js` — `listTags()`: replace the nested-loop uniqueness
26
+ check with a `Set`.
27
+
28
+ ## Acceptance Criteria
29
+
30
+ - [ ] `GET /notes/tags` returns the same tags in first-seen order as before.
31
+ - [ ] The nested loop in `listTags()` is removed.
32
+ - [ ] `npm run smoke` still passes.
33
+
34
+ > A focused, single-function perf fix. The pipeline should confirm output
35
+ > equivalence before and after the change, not just that the test passes.
@@ -0,0 +1,37 @@
1
+ ---
2
+ title: "perf(notes): remove unnecessary O(n²) sort before pagination slice"
3
+ labels: ["performance", "priority:P3"]
4
+ difficulty: "medium"
5
+ ---
6
+
7
+ ## Problem
8
+
9
+ `GET /notes?limit=...&offset=...` runs `db.bubbleSortById(results)` before
10
+ slicing — an O(n²) bubble sort — even though `results` is already in id order
11
+ (the in-memory store is maintained in insertion order and ids are
12
+ monotonically increasing). The sort is both redundant and slow.
13
+
14
+ ## Investigation Hint
15
+
16
+ Trace the insertion order invariant through `db.insert()` and `db.reset()` to
17
+ confirm that `notes` is always in id-ascending order. Then verify that
18
+ `db.bubbleSortById` does not change the element order for already-sorted input
19
+ (it should not — confirm this, don't assume it). Once confirmed, the sort call
20
+ is safe to remove.
21
+
22
+ ## Affected Files
23
+
24
+ 1. `src/routes/notes.js` — `listNotes()`: remove the `db.bubbleSortById(results)`
25
+ call in the pagination branch. Slice `results` directly.
26
+
27
+ ## Acceptance Criteria
28
+
29
+ - [ ] `GET /notes?limit=2` returns the same two notes as before (first two by
30
+ id).
31
+ - [ ] `GET /notes?offset=1&limit=1` returns the same note as before.
32
+ - [ ] The `db.bubbleSortById` call is no longer present in `listNotes()`.
33
+ - [ ] `npm run smoke` still passes.
34
+
35
+ > This issue showcases the **investigation phase confirming an invariant** —
36
+ > the pipeline must verify the insertion-order guarantee before removing the
37
+ > sort, rather than blindly deleting it.
@@ -0,0 +1,53 @@
1
+ ---
2
+ title: "docs(api): add complete API reference covering all endpoints"
3
+ labels: ["documentation", "priority:P3"]
4
+ difficulty: "easy"
5
+ ---
6
+
7
+ ## Problem
8
+
9
+ The demo API now has 9 endpoints (health, list, count, tags, get, create, patch,
10
+ delete, and the bulk/archive/restore endpoints added by later issues) but only
11
+ the original 5 are described in issue #5's `docs/API.md` — and issue #5 may not
12
+ have been run yet. Either way, the current baseline has no `docs/API.md`, or an
13
+ incomplete one.
14
+
15
+ ## Proposed Solution
16
+
17
+ Create (or rewrite) `docs/API.md` documenting all endpoints in the **current
18
+ baseline** (i.e. after the source-code changes in this PR land):
19
+
20
+ | Method | Path | Auth | Description |
21
+ |--------|------|------|-------------|
22
+ | GET | `/health` | no | Liveness check |
23
+ | GET | `/notes` | no | List notes (supports `?where=`, `?tag=`, `?limit=`, `?offset=`) |
24
+ | GET | `/notes/count` | no | Count notes (supports `?owner=`, `?secret=`) |
25
+ | GET | `/notes/tags` | no | List all unique tags |
26
+ | GET | `/notes/:id` | no | Fetch one note |
27
+ | POST | `/notes` | yes | Create a note |
28
+ | PATCH | `/notes/:id` | yes | Update title/body |
29
+ | DELETE | `/notes/:id` | no* | Delete a note |
30
+
31
+ Include a `curl` example for each endpoint, note the intentional flaws inline
32
+ (e.g. `?where=` is injection-vulnerable — see issue #2), and describe the
33
+ `Authorization: Bearer demo-token` header.
34
+
35
+ `*` DELETE is intentionally unprotected — document this as a known flaw (issue #1).
36
+
37
+ ## Affected Files
38
+
39
+ 1. `docs/API.md` (NEW or REWRITE) — full endpoint reference.
40
+ 2. `README.md` — add a "API reference: docs/API.md" link in the "What's inside"
41
+ section if not already present.
42
+
43
+ ## Acceptance Criteria
44
+
45
+ - [ ] `docs/API.md` documents all 8 baseline endpoints with method, path, auth
46
+ requirement, description, and a `curl` example.
47
+ - [ ] The intentional flaws (injection, missing auth on DELETE) are called out
48
+ inline with references to the corresponding issue numbers.
49
+ - [ ] `README.md` links to `docs/API.md`.
50
+ - [ ] No code changes required.
51
+
52
+ > Non-code work that still goes through the full pipeline — investigate the
53
+ > current route surface, write accurate docs, review for completeness.
@@ -0,0 +1,46 @@
1
+ ---
2
+ title: "docs(demo): document the intentional-flaw and graded-issue authoring convention"
3
+ labels: ["documentation", "priority:P3"]
4
+ difficulty: "easy"
5
+ ---
6
+
7
+ ## Problem
8
+
9
+ The demo corpus relies on a deliberate convention — intentional flaws in source
10
+ code, graded issue difficulty, DEMO NOTE comments — but this convention is
11
+ undocumented outside of the source code comments themselves. Someone contributing
12
+ new issues, or using this corpus as a template for their own eval harness, has no
13
+ single reference explaining how the pieces fit together.
14
+
15
+ ## Proposed Solution
16
+
17
+ Create `docs/CORPUS-AUTHORING.md` that explains:
18
+
19
+ 1. **The intentional-flaw pattern** — why flaws are introduced in baseline
20
+ source code and how to mark them with `>>> DEMO NOTE:` comments.
21
+ 2. **The graded-difficulty taxonomy** — the five categories (bug, feature,
22
+ refactor, perf, docs) and the three difficulty tiers (easy, medium, hard),
23
+ with one example per category from this corpus.
24
+ 3. **Issue spec format** — the required YAML frontmatter fields (`title`,
25
+ `labels`, `difficulty`), the required markdown sections (`## Problem`,
26
+ `## Affected Files`, `## Acceptance Criteria`), and the closing `> ...`
27
+ callout paragraph.
28
+ 4. **Independence constraint** — how to verify that a new issue is resolvable
29
+ against the baseline commit without depending on other unmerged issues.
30
+
31
+ ## Affected Files
32
+
33
+ 1. `docs/CORPUS-AUTHORING.md` (NEW).
34
+ 2. `README.md` — add a link to `docs/CORPUS-AUTHORING.md` in a "Contributing
35
+ issues" sub-section.
36
+
37
+ ## Acceptance Criteria
38
+
39
+ - [ ] `docs/CORPUS-AUTHORING.md` covers all four sections listed above.
40
+ - [ ] Each section references at least one existing issue from the corpus as a
41
+ concrete example.
42
+ - [ ] `README.md` links to the new doc.
43
+ - [ ] No code changes required.
44
+
45
+ > A meta-doc that makes the demo self-explaining. Useful for anyone wanting to
46
+ > extend the corpus or use it as a template for their own benchmark.
@@ -48,8 +48,25 @@ async function main() {
48
48
  const unauth = await request(port, 'POST', '/notes', { json: { title: 'nope' } });
49
49
  assert.strictEqual(unauth.status, 401, 'POST without token returns 401');
50
50
 
51
+ const count = await request(port, 'GET', '/notes/count');
52
+ assert.strictEqual(typeof count.body.count, 'number', 'GET /notes/count returns a number');
53
+
54
+ const tags = await request(port, 'GET', '/notes/tags');
55
+ assert.ok(Array.isArray(tags.body.tags), 'GET /notes/tags returns an array');
56
+
57
+ const page = await request(port, 'GET', '/notes?limit=1');
58
+ assert.ok(page.body.notes.length <= 1, 'GET /notes?limit=1 returns at most one note');
59
+
60
+ const noteId = created.body.note.id;
61
+ const patched = await request(port, 'PATCH', `/notes/${noteId}`, {
62
+ token: 'demo-token',
63
+ json: { title: 'smoke (patched)' },
64
+ });
65
+ assert.strictEqual(patched.status, 200, 'PATCH with token returns 200');
66
+ assert.strictEqual(patched.body.note.title, 'smoke (patched)', 'PATCH updates the title');
67
+
51
68
  // eslint-disable-next-line no-console
52
- console.log('SMOKE OK — health, list, create(auth), create(unauth) all pass');
69
+ console.log('SMOKE OK — health, list, create(auth), create(unauth), count, tags, pagination, patch all pass');
53
70
  server.close();
54
71
  }
55
72
 
@@ -10,12 +10,14 @@
10
10
  // >>> It exists only inside examples/forgedock-demo so the review agents have
11
11
  // >>> something to catch. Do not copy this pattern into real code.
12
12
 
13
- let notes = [
14
- { id: 1, owner: 'alice', title: 'Buy milk', body: 'Whole milk, 2 liters', secret: false },
15
- { id: 2, owner: 'alice', title: 'Launch plan', body: 'Ship the demo repo', secret: true },
16
- { id: 3, owner: 'bob', title: 'Gym', body: 'Leg day', secret: false },
13
+ const SEED = [
14
+ { id: 1, owner: 'alice', title: 'Buy milk', body: 'Whole milk, 2 liters', secret: false, tags: ['errand'], archived: false, createdAt: '2024-01-01T00:00:00.000Z' },
15
+ { id: 2, owner: 'alice', title: 'Launch plan', body: 'Ship the demo repo', secret: true, tags: ['work'], archived: false, createdAt: '2024-01-02T00:00:00.000Z' },
16
+ { id: 3, owner: 'bob', title: 'Gym', body: 'Leg day', secret: false, tags: ['fitness', 'errand'], archived: false, createdAt: '2024-01-03T00:00:00.000Z' },
17
17
  ];
18
18
 
19
+ let notes = SEED.map((n) => ({ ...n }));
20
+
19
21
  let nextId = 4;
20
22
 
21
23
  function all() {
@@ -43,7 +45,13 @@ function query(whereClause) {
43
45
  }
44
46
 
45
47
  function insert(note) {
46
- const record = { id: nextId++, secret: false, ...note };
48
+ const record = { id: nextId++, secret: false, tags: [], archived: false, createdAt: new Date().toISOString() };
49
+ // Only override a default when the caller actually supplied the key — a
50
+ // spread of an object containing an explicit `undefined` would otherwise
51
+ // silently clobber the default (e.g. `tags: undefined`).
52
+ for (const [key, value] of Object.entries(note)) {
53
+ if (value !== undefined) record[key] = value;
54
+ }
47
55
  notes.push(record);
48
56
  return record;
49
57
  }
@@ -55,12 +63,29 @@ function remove(id) {
55
63
  }
56
64
 
57
65
  function reset() {
58
- notes = [
59
- { id: 1, owner: 'alice', title: 'Buy milk', body: 'Whole milk, 2 liters', secret: false },
60
- { id: 2, owner: 'alice', title: 'Launch plan', body: 'Ship the demo repo', secret: true },
61
- { id: 3, owner: 'bob', title: 'Gym', body: 'Leg day', secret: false },
62
- ];
66
+ notes = SEED.map((n) => ({ ...n }));
63
67
  nextId = 4;
64
68
  }
65
69
 
66
- module.exports = { all, findById, query, insert, remove, reset };
70
+ // Returns a NEW array sorted by `id` ascending — does not mutate `arr`.
71
+ //
72
+ // >>> DEMO NOTE: bubble sort is an INTENTIONAL, isolated inefficiency (O(n^2))
73
+ // >>> kept here so the review/perf pipeline has a real hot path to optimize.
74
+ // >>> `notes` is already maintained in insertion (id) order, so callers that
75
+ // >>> only need "sorted by id" rarely need this at all — see the perf issue
76
+ // >>> that targets its one caller in routes/notes.js.
77
+ function bubbleSortById(arr) {
78
+ const copy = arr.slice();
79
+ for (let i = 0; i < copy.length; i++) {
80
+ for (let j = 0; j < copy.length - i - 1; j++) {
81
+ if (copy[j].id > copy[j + 1].id) {
82
+ const tmp = copy[j];
83
+ copy[j] = copy[j + 1];
84
+ copy[j + 1] = tmp;
85
+ }
86
+ }
87
+ }
88
+ return copy;
89
+ }
90
+
91
+ module.exports = { all, findById, query, insert, remove, reset, bubbleSortById };
@@ -8,17 +8,80 @@ const db = require('../db');
8
8
  const { requireToken } = require('../auth');
9
9
 
10
10
  // GET /notes
11
- // Lists notes. Supports an optional ?where= filter that is passed straight
12
- // into db.query().
11
+ // Lists notes. Supports:
12
+ // ?where= — raw expression passed straight into db.query() (see db.js)
13
+ // ?tag= — exact match against a note's `tags` array
14
+ // ?limit=, ?offset= — pagination
13
15
  //
14
16
  // >>> DEMO NOTE: forwarding the raw ?where= string into db.query() is the
15
17
  // >>> injection surface (see db.js). Issue #2 asks to make this safe.
16
18
  function listNotes(req, res, params) {
17
- const where = params.query.where;
18
- if (where) {
19
- return { notes: db.query(where) };
19
+ const { where, tag, limit, offset } = params.query;
20
+
21
+ let results = where ? db.query(where) : db.all();
22
+
23
+ if (tag) {
24
+ // >>> DEMO NOTE: `tags` is accepted but never validated as an array at
25
+ // >>> write time (see createNote below). A note created with a
26
+ // >>> malformed `tags` value makes this `.includes()` call throw instead
27
+ // >>> of being skipped safely. INTENTIONAL, isolated flaw.
28
+ results = results.filter((n) => n.tags.includes(tag));
29
+ }
30
+
31
+ if (limit !== undefined || offset !== undefined) {
32
+ // >>> DEMO NOTE: `offset` is not clamped to a non-negative value, so a
33
+ // >>> negative offset falls through to Array.prototype.slice's
34
+ // >>> negative-index semantics (counts from the end) instead of being
35
+ // >>> rejected or normalized to 0. INTENTIONAL, isolated flaw.
36
+ const off = Number(offset) || 0;
37
+ const lim = Number(limit);
38
+ // Sorting by id here is redundant — `results` is already in id order —
39
+ // but the demo intentionally uses the O(n^2) helper anyway (see db.js).
40
+ const sorted = db.bubbleSortById(results);
41
+ results = sorted.slice(off, Number.isFinite(lim) ? off + lim : undefined);
42
+ }
43
+
44
+ return { notes: results };
45
+ }
46
+
47
+ // GET /notes/count
48
+ // Returns { count } for notes matching optional ?owner= / ?secret= filters.
49
+ //
50
+ // >>> DEMO NOTE: builds its where-clause with naive string concatenation and
51
+ // >>> reuses the vulnerable db.query() eval path — the same injection class
52
+ // >>> issue #2 asks you to remove from listNotes, but reintroduced here in a
53
+ // >>> sibling endpoint issue #2's scope doesn't touch. INTENTIONAL, isolated
54
+ // >>> flaw kept for the benchmark corpus.
55
+ function countNotes(req, res, params) {
56
+ const { owner, secret } = params.query;
57
+ const clauses = [];
58
+ if (owner) clauses.push('owner === "' + owner + '"');
59
+ if (secret !== undefined) clauses.push('secret === ' + (secret === 'true'));
60
+ const whereClause = clauses.length ? clauses.join(' && ') : 'true';
61
+ return { count: db.query(whereClause).length };
62
+ }
63
+
64
+ // GET /notes/tags
65
+ // Returns the de-duplicated list of tags across all notes.
66
+ //
67
+ // >>> DEMO NOTE: de-dupes with a nested loop (checks every tag against every
68
+ // >>> unique tag seen so far) instead of a Set — O(n^2) on the total tag
69
+ // >>> count. INTENTIONAL, isolated inefficiency kept for the perf corpus.
70
+ function listTags(req, res, params) {
71
+ const unique = [];
72
+ for (const note of db.all()) {
73
+ for (const t of note.tags || []) {
74
+ let seen = false;
75
+ for (const u of unique) {
76
+ if (u === t) {
77
+ seen = true;
78
+ break;
79
+ }
80
+ }
81
+ if (!seen) unique.push(t);
82
+ }
20
83
  }
21
- return { notes: db.all() };
84
+ return { tags: unique };
22
85
  }
23
86
 
24
87
  // GET /notes/:id
@@ -33,19 +96,53 @@ function getNote(req, res, params) {
33
96
  }
34
97
 
35
98
  // POST /notes (protected)
99
+ //
100
+ // >>> DEMO NOTE: accepts a `tags` field but never checks that it's an array
101
+ // >>> before storing it. A malformed value (e.g. a number) is stored as-is
102
+ // >>> and later crashes GET /notes?tag= (see listNotes). INTENTIONAL,
103
+ // >>> isolated flaw kept for the benchmark corpus.
36
104
  function createNote(req, res, params) {
37
105
  requireToken(req);
38
- const { title, body, owner } = params.body || {};
106
+ const { title, body, owner, tags } = params.body || {};
39
107
  if (!title) {
40
108
  const err = new Error('title is required');
41
109
  err.statusCode = 400;
42
110
  throw err;
43
111
  }
44
- const note = db.insert({ title, body: body || '', owner: owner || 'anonymous' });
112
+ const payload = { title, body: body || '', owner: owner || 'anonymous' };
113
+ if (tags !== undefined) payload.tags = tags;
114
+ const note = db.insert(payload);
45
115
  res.statusCode = 201;
46
116
  return { note };
47
117
  }
48
118
 
119
+ // PATCH /notes/:id (protected)
120
+ //
121
+ // >>> DEMO NOTE: applies `owner` and `secret` from the request body
122
+ // >>> unconditionally alongside `title`/`body`. Any caller holding the
123
+ // >>> shared demo token can reassign a note's ownership or flip its
124
+ // >>> `secret` flag to false, exposing it via the unauthenticated
125
+ // >>> GET /notes listing. This is a mass-assignment / over-permissive-write
126
+ // >>> flaw, distinct from issue #1's missing-auth-check pattern.
127
+ // >>> INTENTIONAL, isolated flaw kept for the benchmark corpus. Note that
128
+ // >>> `tags` is NOT among the fields this handler updates yet — that gap is
129
+ // >>> a separate, not-yet-built feature.
130
+ function updateNote(req, res, params) {
131
+ requireToken(req);
132
+ const note = db.findById(params.id);
133
+ if (!note) {
134
+ const err = new Error('Note not found');
135
+ err.statusCode = 404;
136
+ throw err;
137
+ }
138
+ const { title, body, owner, secret } = params.body || {};
139
+ if (title !== undefined) note.title = title;
140
+ if (body !== undefined) note.body = body;
141
+ if (owner !== undefined) note.owner = owner;
142
+ if (secret !== undefined) note.secret = secret;
143
+ return { note };
144
+ }
145
+
49
146
  // DELETE /notes/:id
50
147
  //
51
148
  // >>> DEMO NOTE: this mutating route is MISSING a requireToken(req) call, so
@@ -61,4 +158,4 @@ function deleteNote(req, res, params) {
61
158
  return { deleted: Number(params.id) };
62
159
  }
63
160
 
64
- module.exports = { listNotes, getNote, createNote, deleteNote };
161
+ module.exports = { listNotes, countNotes, listTags, getNote, createNote, updateNote, deleteNote };
@@ -14,7 +14,10 @@ const PORT = process.env.PORT || 3000;
14
14
  const TABLE = [
15
15
  ['GET', /^\/notes$/, routes.listNotes],
16
16
  ['POST', /^\/notes$/, routes.createNote],
17
+ ['GET', /^\/notes\/count$/, routes.countNotes],
18
+ ['GET', /^\/notes\/tags$/, routes.listTags],
17
19
  ['GET', /^\/notes\/(?<id>\d+)$/, routes.getNote],
20
+ ['PATCH', /^\/notes\/(?<id>\d+)$/, routes.updateNote],
18
21
  ['DELETE', /^\/notes\/(?<id>\d+)$/, routes.deleteNote],
19
22
  ];
20
23
 
@@ -58,7 +61,7 @@ const server = http.createServer(async (req, res) => {
58
61
 
59
62
  const [, pattern, handler] = match;
60
63
  const groups = url.pathname.match(pattern).groups || {};
61
- const body = req.method === 'POST' ? await readBody(req) : {};
64
+ const body = req.method === 'POST' || req.method === 'PATCH' ? await readBody(req) : {};
62
65
 
63
66
  try {
64
67
  const result = handler(req, res, { ...groups, query, body });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forgedock",
3
- "version": "1.0.22",
3
+ "version": "1.1.2",
4
4
  "description": "Autonomous dev pipeline for Claude Code. GitHub as institutional memory — every issue, PR, and annotation survives context resets and makes the next agent smarter.",
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://forgedock.dev",
30
+ "homepage": "https://github.com/RapierCraftStudios/ForgeDock#readme",
31
31
  "bugs": {
32
32
  "url": "https://github.com/RapierCraftStudios/ForgeDock/issues"
33
33
  },
@@ -36,20 +36,28 @@
36
36
  "!bin/tests/",
37
37
  "commands/",
38
38
  "docs/",
39
+ "!docs/site/.vitepress/",
39
40
  "examples/",
41
+ "packages/protocol/src/",
40
42
  "scripts/",
41
43
  "templates/",
42
44
  "LICENSE",
43
45
  "README.md"
44
46
  ],
45
47
  "scripts": {
46
- "test": "node --test \"bin/tests/**/*.test.mjs\""
48
+ "test": "node --test \"bin/tests/**/*.test.mjs\"",
49
+ "docs:dev": "vitepress dev docs/site",
50
+ "docs:build": "vitepress build docs/site",
51
+ "docs:preview": "vitepress preview docs/site"
47
52
  },
48
53
  "engines": {
49
54
  "node": ">=18"
50
55
  },
51
56
  "optionalDependencies": {
52
- "@anthropic-ai/sdk": ">=0.30.0"
57
+ "@anthropic-ai/sdk": ">=0.30.0 <1"
58
+ },
59
+ "devDependencies": {
60
+ "vitepress": "^1.6.3"
53
61
  },
54
62
  "peerDependencies": {
55
63
  "@anthropic-ai/claude-code": ">=2.0.0"
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 RapierCraft Studios
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.