forgedock 1.1.1 → 1.1.3

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 (174) hide show
  1. package/README.md +53 -8
  2. package/bin/batch-runner.mjs +339 -0
  3. package/bin/demo.mjs +92 -7
  4. package/bin/engine/phases.mjs +24 -2
  5. package/bin/engine/state.mjs +8 -3
  6. package/bin/engine-cli.mjs +177 -5
  7. package/bin/forgedock.mjs +409 -26
  8. package/bin/hooks/interactive-engine.mjs +509 -0
  9. package/bin/hooks/pre-tool-use.mjs +498 -0
  10. package/bin/hooks/subagent-stop-enforce.mjs +197 -0
  11. package/bin/journey.mjs +72 -14
  12. package/bin/registry.mjs +139 -0
  13. package/bin/report.mjs +447 -0
  14. package/bin/runner.mjs +70 -20
  15. package/bin/settings-hook.mjs +235 -1
  16. package/commands/adopt.md +4 -1
  17. package/commands/analytics.md +4 -1
  18. package/commands/audit-agents.md +110 -27
  19. package/commands/audit.md +20 -1
  20. package/commands/autopilot.md +518 -361
  21. package/commands/changelog.md +2 -1
  22. package/commands/ci-audit.md +2 -1
  23. package/commands/cleanup.md +161 -1
  24. package/commands/compat-audit.md +2 -1
  25. package/commands/deploy-info.md +58 -1
  26. package/commands/deploy-pr.md +485 -0
  27. package/commands/design-bench.md +5 -1
  28. package/commands/design-render-critique-loop.md +1 -1
  29. package/commands/diagnose.md +2 -1
  30. package/commands/explain.md +2 -1
  31. package/commands/fix-ci.md +577 -0
  32. package/commands/forge-stats.md +121 -2
  33. package/commands/forgedock-init.md +1 -1
  34. package/commands/geo-audit.md +3 -0
  35. package/commands/incident-response.md +2 -1
  36. package/commands/issue.md +23 -2
  37. package/commands/milestone.md +77 -12
  38. package/commands/optimize.md +2 -1
  39. package/commands/orchestrate/config.md +67 -0
  40. package/commands/orchestrate/phase-1-resolve.md +159 -0
  41. package/commands/orchestrate/phase-2-triage.md +126 -0
  42. package/commands/orchestrate/phase-2.5-synthesis.md +156 -0
  43. package/commands/orchestrate/phase-3-dependency.md +591 -0
  44. package/commands/orchestrate/phase-4-execution.md +962 -0
  45. package/commands/orchestrate/phase-5-cleanup.md +47 -0
  46. package/commands/orchestrate/phase-6-report.md +198 -0
  47. package/commands/orchestrate/safety.md +62 -0
  48. package/commands/orchestrate.md +25 -2045
  49. package/commands/pipeline-health/config.md +34 -0
  50. package/commands/pipeline-health/phase-1-context.md +180 -0
  51. package/commands/pipeline-health/phase-2-metrics.md +1729 -0
  52. package/commands/pipeline-health/phase-3-analyze.md +204 -0
  53. package/commands/pipeline-health/phase-4-proposals.md +117 -0
  54. package/commands/pipeline-health/phase-5-report.md +284 -0
  55. package/commands/pipeline-health/phase-6-summary.md +52 -0
  56. package/commands/pipeline-health.md +25 -2401
  57. package/commands/pipeline-resume.md +24 -1
  58. package/commands/pipeline-status.md +65 -0
  59. package/commands/qa-sweep.md +4 -1
  60. package/commands/quality-gate.md +248 -10
  61. package/commands/recover-orphans.md +576 -0
  62. package/commands/replay.md +2 -1
  63. package/commands/review-pr-agents/api.md +135 -0
  64. package/commands/review-pr-agents/auth.md +137 -0
  65. package/commands/review-pr-agents/billing.md +101 -0
  66. package/commands/review-pr-agents/concurrency.md +145 -0
  67. package/commands/review-pr-agents/database.md +103 -0
  68. package/commands/review-pr-agents/frontend.md +89 -0
  69. package/commands/review-pr-agents/infra.md +385 -0
  70. package/commands/review-pr-agents/protocols.md +185 -0
  71. package/commands/review-pr-agents/scraper.md +185 -0
  72. package/commands/review-pr-agents/security.md +406 -0
  73. package/commands/review-pr-agents.md +45 -1722
  74. package/commands/review-pr-staging.md +196 -40
  75. package/commands/review-pr.md +591 -52
  76. package/commands/rollback.md +2 -1
  77. package/commands/scope.md +2 -1
  78. package/commands/security-audit.md +2 -1
  79. package/commands/signal-planner.md +623 -0
  80. package/commands/sync-ecosystem.md +1 -0
  81. package/commands/test-gate.md +17 -1
  82. package/commands/validate.md +2 -1
  83. package/commands/work-on/build/architect.md +28 -0
  84. package/commands/work-on/build/context.md +2 -0
  85. package/commands/work-on/build/implement.md +3 -1
  86. package/commands/work-on/build/validate.md +26 -7
  87. package/commands/work-on/build.md +163 -24
  88. package/commands/work-on/close.md +130 -3
  89. package/commands/work-on/decompose.md +19 -1
  90. package/commands/work-on/investigate.md +117 -2
  91. package/commands/work-on/review.md +24 -4
  92. package/commands/work-on-monolithic.md +7 -2
  93. package/commands/work-on.md +202 -4
  94. package/docs/BRAND.md +14 -7
  95. package/docs/CI.md +16 -2
  96. package/docs/CODEX.md +36 -1
  97. package/docs/CONFIG.md +5 -8
  98. package/docs/FORGE-PROTOCOL.md +15 -1064
  99. package/docs/anthropic-devrel-outreach.md +2 -2
  100. package/docs/articles/command-decomposition-token-savings.md +214 -0
  101. package/docs/articles/model-release-playbook.md +131 -0
  102. package/docs/articles/per-repo-adaptive-scripts-token-savings.md +27 -25
  103. package/docs/claude-breakpoints.json +75 -5
  104. package/docs/design/abc-benchmark.md +1 -1
  105. package/docs/design/design-spec-schema.md +1 -1
  106. package/docs/design/divergent-generation.md +1 -1
  107. package/docs/design/eval-gate-runbook.md +178 -0
  108. package/docs/design/show-hn-draft.md +26 -1
  109. package/docs/design/token-cost-baseline-1298.md +59 -0
  110. package/docs/eval/README.md +120 -0
  111. package/docs/eval/corpus/seed-v1.json +6 -0
  112. package/docs/eval/published/claude-sonnet-4-6-2026-07-04.json +33 -0
  113. package/docs/site/annotations-explained.md +24 -0
  114. package/docs/site/command-learning-path.md +51 -14
  115. package/docs/site/command-reference.md +386 -111
  116. package/docs/site/for-companies.md +95 -0
  117. package/docs/site/forge-annotation-protocol.md +12 -256
  118. package/docs/site/getting-started.md +43 -4
  119. package/docs/site/github-is-the-memory.md +113 -0
  120. package/docs/site/how-it-works.md +3 -13
  121. package/docs/site/index.md +9 -1
  122. package/docs/spec/eval-run-result.md +147 -0
  123. package/docs/spec/forge-protocol-v1.md +441 -18
  124. package/docs/spec/label-state-machine.md +55 -0
  125. package/docs/spec/review-protocol.md +158 -0
  126. package/docs/spec-graph-schema.md +28 -0
  127. package/docs/superpowers/specs/2026-07-04-economic-self-governance-design.md +181 -0
  128. package/docs/superpowers/specs/2026-07-04-provenance-trail-design.md +189 -0
  129. package/examples/forgedock-demo/README.md +41 -22
  130. package/examples/forgedock-demo/issues/06-bug-tags-type-validation.md +43 -0
  131. package/examples/forgedock-demo/issues/07-bug-negative-offset-pagination.md +42 -0
  132. package/examples/forgedock-demo/issues/08-bug-patch-mass-assignment.md +49 -0
  133. package/examples/forgedock-demo/issues/09-bug-count-injection.md +47 -0
  134. package/examples/forgedock-demo/issues/10-feature-sort-param.md +39 -0
  135. package/examples/forgedock-demo/issues/11-feature-patch-tags.md +37 -0
  136. package/examples/forgedock-demo/issues/12-feature-bulk-create.md +42 -0
  137. package/examples/forgedock-demo/issues/13-feature-archive-notes.md +46 -0
  138. package/examples/forgedock-demo/issues/14-refactor-extract-title-validation.md +41 -0
  139. package/examples/forgedock-demo/issues/15-refactor-protected-route-flag.md +43 -0
  140. package/examples/forgedock-demo/issues/16-refactor-extract-store.md +45 -0
  141. package/examples/forgedock-demo/issues/17-perf-tags-dedup-set.md +35 -0
  142. package/examples/forgedock-demo/issues/18-perf-remove-redundant-sort.md +37 -0
  143. package/examples/forgedock-demo/issues/19-docs-api-reference-full.md +53 -0
  144. package/examples/forgedock-demo/issues/20-docs-intentional-flaw-convention.md +46 -0
  145. package/examples/forgedock-demo/scripts/smoke.js +18 -1
  146. package/examples/forgedock-demo/src/db.js +36 -11
  147. package/examples/forgedock-demo/src/routes/notes.js +106 -9
  148. package/examples/forgedock-demo/src/server.js +4 -1
  149. package/package.json +3 -1
  150. package/packages/protocol/LICENSE +21 -0
  151. package/packages/protocol/src/cli.js +135 -0
  152. package/packages/protocol/src/emit.js +146 -0
  153. package/packages/protocol/src/index.js +43 -0
  154. package/packages/protocol/src/parse.js +254 -0
  155. package/packages/protocol/src/types.js +171 -0
  156. package/packages/protocol/src/validate.js +122 -0
  157. package/scripts/bench-topology-cost.mjs +372 -0
  158. package/scripts/check-command-docs-drift.sh +198 -0
  159. package/scripts/check-protocol-restatements.sh +176 -0
  160. package/scripts/check-registry/README.md +72 -0
  161. package/scripts/check-registry/manifest.json +6 -0
  162. package/scripts/code-index.sh +454 -0
  163. package/scripts/conformance-check.mjs +168 -0
  164. package/scripts/doctor-pipeline-state.sh +484 -0
  165. package/scripts/eval-gate-baseline.json +17 -0
  166. package/scripts/eval-gate-scorecard.mjs +242 -0
  167. package/scripts/eval-scorecard-aggregator.mjs +252 -0
  168. package/scripts/flaky-quarantine.sh +241 -0
  169. package/scripts/forge-annotation.sh +567 -0
  170. package/scripts/issue-dedup.sh +236 -0
  171. package/scripts/validate-annotation-node.mjs +233 -0
  172. package/scripts/worktree-lifecycle.sh +3 -3
  173. package/templates/devdocs/agent/using-forgedock.md +14 -0
  174. package/docs/site/.vitepress/config.mjs +0 -85
@@ -0,0 +1,236 @@
1
+ #!/usr/bin/env bash
2
+ # issue-dedup.sh — Deterministic near-duplicate check for GitHub issue creation
3
+ #
4
+ # Usage:
5
+ # issue-dedup.sh <title> [-R <owner/repo>] [--force]
6
+ # issue-dedup.sh <title> ["-R <owner/repo>"] [--force] (single pre-joined token also accepted)
7
+ #
8
+ # <title> : Proposed issue title (required, must be quoted if it contains spaces)
9
+ # -R <owner/repo> : GitHub repository, as two argv tokens (optional, defaults to
10
+ # FORGE_REPO or current repo)
11
+ # "-R <owner/repo>" : GitHub repository, as ONE pre-joined token (e.g. when a caller
12
+ # passes an already-composed $GH_FLAG variable quoted as "$GH_FLAG").
13
+ # Both forms are accepted. A title that happens to start with "-R "
14
+ # but lacks the "/" separator (e.g. "-R login is broken") is
15
+ # correctly detected by the parser and treated as a title, not a
16
+ # repo flag. See the -R\ * case arm guard in the arg-parsing loop.
17
+ # --force : Skip dedup check and always allow creation (explicit override)
18
+ #
19
+ # Exit codes:
20
+ # 0 — No near-duplicate found. Creation is safe. Stdout is empty.
21
+ # 1 — Near-duplicate found. Stdout contains the matched issue number and title.
22
+ # Callers MUST check exit code before running `gh issue create`.
23
+ # 2 — Usage error (missing required argument).
24
+ #
25
+ # Algorithm (deterministic — no LLM required):
26
+ # 1. Normalize the proposed title: lowercase, strip punctuation, split into tokens
27
+ # of length ≥ 4 (short words are noise: "fix", "add", "the", etc.)
28
+ # 2. Query open issues: search GitHub using the 2 most distinctive title tokens
29
+ # as the --search query (balances recall vs API cost)
30
+ # 3. For each candidate open issue, count shared tokens (length ≥ 4) between
31
+ # the proposed title and the candidate title.
32
+ # 4. MATCH threshold: ≥ 3 shared tokens OR shared token count ≥ 50% of
33
+ # the shorter title's token set — whichever fires first.
34
+ # 5. On match: print "DUPLICATE: #<N> <title>" to stdout, exit 1.
35
+ # 6. On no match: exit 0 (silent).
36
+ #
37
+ # Integration pattern (for command specs that call gh issue create):
38
+ #
39
+ # DEDUP_RESULT=$(scripts/issue-dedup.sh "$PROPOSED_TITLE" "$GH_FLAG" 2>&1)
40
+ # # ^ $GH_FLAG is typically already composed as "-R owner/repo" and quoted here,
41
+ # # producing ONE argv token — the parser accepts this joined form directly
42
+ # # (see the -R\ * case arm below). An unset/empty $GH_FLAG quotes to an empty
43
+ # # token, which the parser also treats as "no repo flag" rather than a title.
44
+ # DEDUP_EXIT=$?
45
+ # if [ $DEDUP_EXIT -eq 1 ]; then
46
+ # echo "Near-duplicate detected: $DEDUP_RESULT"
47
+ # echo "Comment on the existing issue instead of creating a new one."
48
+ # echo "Use --force to override and create anyway."
49
+ # exit 1
50
+ # elif [ $DEDUP_EXIT -eq 2 ]; then
51
+ # echo "Dedup check usage error: $DEDUP_RESULT"
52
+ # echo "Do NOT proceed to issue creation — fix the invocation and retry."
53
+ # exit 2
54
+ # fi
55
+ # gh issue create ... # only runs when exit 0
56
+ #
57
+ # Callers MUST explicitly branch on exit 2 (usage error) as a hard stop, not
58
+ # just exit 1 (duplicate found). Silently falling through on any non-1 exit
59
+ # code — including redirecting stderr to /dev/null — means a malformed
60
+ # invocation (bad flag, missing argument) is indistinguishable from "no
61
+ # duplicate found" and proceeds to create a possibly-duplicate issue with
62
+ # zero operator-visible signal.
63
+ #
64
+ # Notes:
65
+ # - Closed issues are NOT matched (a regression should be filed fresh with a
66
+ # "Regression of #N" reference; see issue.md Phase 2D).
67
+ # - The --force flag is the explicit override path. Human decision is required
68
+ # to use it — agents MUST NOT pass --force without user authorization.
69
+ # - Token matching is case-insensitive and punctuation-agnostic.
70
+ # - GitHub's --search flag performs a full-text search; the token query is a
71
+ # tiebreaker against the full candidate list, not the sole filter.
72
+ #
73
+ # SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios
74
+ # SPDX-License-Identifier: AGPL-3.0-or-later
75
+
76
+ set -euo pipefail
77
+
78
+ # --------------------------------------------------------------------------- #
79
+ # Argument parsing
80
+ # --------------------------------------------------------------------------- #
81
+ PROPOSED_TITLE=""
82
+ GH_FLAG=""
83
+ FORCE=0
84
+
85
+ while [[ $# -gt 0 ]]; do
86
+ case "$1" in
87
+ "")
88
+ # Empty-string token — e.g. a caller quoting an unset/empty $GH_FLAG
89
+ # ("$GH_FLAG" -> ""). Treat as "no repo flag supplied", not as a title.
90
+ shift
91
+ ;;
92
+ -R)
93
+ shift
94
+ if [[ $# -eq 0 ]]; then
95
+ echo "Missing value for -R" >&2
96
+ exit 2
97
+ fi
98
+ GH_FLAG="-R $1"
99
+ shift
100
+ ;;
101
+ -R\ *)
102
+ # Single pre-joined token, e.g. "-R owner/repo" — produced when a caller
103
+ # quotes an already-composed $GH_FLAG variable ("$GH_FLAG") instead of
104
+ # passing -R and the repo as two separate argv tokens. Both call shapes
105
+ # are valid integration patterns used across commands/*.md callers.
106
+ #
107
+ # Guard: a valid repo token always contains '/' (owner/repo format).
108
+ # If the value after "-R " has no '/', the token is a positional title
109
+ # that happens to start with "-R " (e.g. "-R login is broken") — treat
110
+ # it as the title instead. This prevents argv-flag-title-collision (#1625).
111
+ _rval="${1#-R }"
112
+ if [[ "$_rval" == */* ]]; then
113
+ GH_FLAG="$1"
114
+ shift
115
+ else
116
+ PROPOSED_TITLE="$1"
117
+ shift
118
+ fi
119
+ ;;
120
+ --force)
121
+ FORCE=1
122
+ shift
123
+ ;;
124
+ -*)
125
+ echo "Unknown flag: $1" >&2
126
+ exit 2
127
+ ;;
128
+ *)
129
+ PROPOSED_TITLE="$1"
130
+ shift
131
+ ;;
132
+ esac
133
+ done
134
+
135
+ if [[ -z "$PROPOSED_TITLE" ]]; then
136
+ echo "Usage: issue-dedup.sh <title> [-R <owner/repo>] [--force]" >&2
137
+ exit 2
138
+ fi
139
+
140
+ # Force override — skip all checks
141
+ if [[ "$FORCE" -eq 1 ]]; then
142
+ exit 0
143
+ fi
144
+
145
+ # --------------------------------------------------------------------------- #
146
+ # Token normalization
147
+ # Lowercase, strip non-alphanumeric chars except spaces, split on whitespace,
148
+ # keep tokens of length >= 4 to filter out noise words.
149
+ # --------------------------------------------------------------------------- #
150
+ normalize_tokens() {
151
+ local text="$1"
152
+ echo "$text" \
153
+ | tr '[:upper:]' '[:lower:]' \
154
+ | sed 's/[^a-z0-9 ]/ /g' \
155
+ | tr -s ' ' '\n' \
156
+ | awk 'length >= 4' \
157
+ | sort -u
158
+ }
159
+
160
+ PROPOSED_TOKENS=$(normalize_tokens "$PROPOSED_TITLE")
161
+ # grep -c always prints a numeric count (including "0") even when it matches
162
+ # nothing — it just exits 1 in that case. `|| echo 0` would append a SECOND
163
+ # "0" line on top of grep's own "0" output, corrupting the numeric comparison
164
+ # below. `|| true` only suppresses the non-zero exit under `set -e`.
165
+ PROPOSED_TOKEN_COUNT=$(echo "$PROPOSED_TOKENS" | grep -c . || true)
166
+
167
+ if [[ "$PROPOSED_TOKEN_COUNT" -lt 1 ]]; then
168
+ # Title has no meaningful tokens — skip dedup (cannot match)
169
+ exit 0
170
+ fi
171
+
172
+ # Build a search query from the 2 most distinctive tokens (longest first)
173
+ SEARCH_TERMS=$(echo "$PROPOSED_TOKENS" | awk '{ print length, $0 }' | sort -rn | awk '{print $2}' | head -2 | tr '\n' ' ')
174
+ SEARCH_QUERY=$(echo "$SEARCH_TERMS" | xargs)
175
+
176
+ # --------------------------------------------------------------------------- #
177
+ # Query open issues
178
+ # --------------------------------------------------------------------------- #
179
+ CANDIDATES=$(gh issue list $GH_FLAG \
180
+ --state open \
181
+ --search "$SEARCH_QUERY" \
182
+ --limit 30 \
183
+ --json number,title \
184
+ --jq '.[] | "\(.number)\t\(.title)"' 2>/dev/null || true)
185
+
186
+ if [[ -z "$CANDIDATES" ]]; then
187
+ exit 0
188
+ fi
189
+
190
+ # --------------------------------------------------------------------------- #
191
+ # Token overlap matching
192
+ # For each candidate, count shared tokens between proposed and candidate titles.
193
+ # Match if: shared_count >= 3 OR shared_count >= 50% of the shorter token set.
194
+ # --------------------------------------------------------------------------- #
195
+ MATCH_NUMBER=""
196
+ MATCH_TITLE=""
197
+
198
+ while IFS=$'\t' read -r CAND_NUMBER CAND_TITLE; do
199
+ [[ -z "$CAND_NUMBER" ]] && continue
200
+
201
+ CAND_TOKENS=$(normalize_tokens "$CAND_TITLE")
202
+ # Same double-fallback hazard as PROPOSED_TOKEN_COUNT above — see comment there.
203
+ CAND_TOKEN_COUNT=$(echo "$CAND_TOKENS" | grep -c . || true)
204
+
205
+ # Shared token count: intersection of proposed and candidate token sets
206
+ SHARED_COUNT=$(comm -12 \
207
+ <(echo "$PROPOSED_TOKENS") \
208
+ <(echo "$CAND_TOKENS") \
209
+ | grep -c . || true)
210
+
211
+ # Shorter token set size (for percentage threshold)
212
+ if [[ "$PROPOSED_TOKEN_COUNT" -le "$CAND_TOKEN_COUNT" ]]; then
213
+ SHORTER=$PROPOSED_TOKEN_COUNT
214
+ else
215
+ SHORTER=$CAND_TOKEN_COUNT
216
+ fi
217
+
218
+ # 50% threshold: shared >= ceil(shorter / 2)
219
+ HALF_THRESHOLD=$(( (SHORTER + 1) / 2 ))
220
+
221
+ if [[ "$SHARED_COUNT" -ge 3 ]] || [[ "$SHARED_COUNT" -ge "$HALF_THRESHOLD" && "$SHORTER" -ge 2 ]]; then
222
+ MATCH_NUMBER="$CAND_NUMBER"
223
+ MATCH_TITLE="$CAND_TITLE"
224
+ break
225
+ fi
226
+ done <<< "$CANDIDATES"
227
+
228
+ # --------------------------------------------------------------------------- #
229
+ # Output result
230
+ # --------------------------------------------------------------------------- #
231
+ if [[ -n "$MATCH_NUMBER" ]]; then
232
+ echo "DUPLICATE: #${MATCH_NUMBER} ${MATCH_TITLE}"
233
+ exit 1
234
+ fi
235
+
236
+ exit 0
@@ -0,0 +1,233 @@
1
+ #!/usr/bin/env node
2
+ // SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios
3
+ // SPDX-License-Identifier: MIT
4
+ //
5
+ // validate-annotation-node.mjs — Thin Node.js adapter: validates FORGE annotation bodies
6
+ // against the MIT/Apache protocol validation library (packages/protocol/, built in #1291).
7
+ //
8
+ // This script is the dogfooding consumer for the protocol library (#1291), satisfying
9
+ // the requirement from #1292. It wraps the library's validate() API with:
10
+ // - Graceful degradation when the library is not yet installed (exits 0 with WARN)
11
+ // - A stable CLI that matches forge-annotation.sh validate's argument shape,
12
+ // so future forge-annotation.sh validate integration (#1267) is a one-line shim
13
+ // - MIT license: this file is NOT AGPL — it must remain importable by MIT/Apache
14
+ // consumers without license contamination. Do not import AGPL modules here.
15
+ //
16
+ // Usage:
17
+ // node scripts/validate-annotation-node.mjs <MARKER> [FILE]
18
+ // echo '<!-- FORGE:INVESTIGATOR -->...' | node scripts/validate-annotation-node.mjs INVESTIGATOR
19
+ //
20
+ // Arguments:
21
+ // MARKER Annotation type to validate (e.g. INVESTIGATOR, CONTRACT, BUILDER).
22
+ // May include the FORGE: prefix — it will be stripped automatically.
23
+ // FILE Path to a file containing the annotation body. If omitted (or '-'),
24
+ // reads from stdin.
25
+ //
26
+ // Exit codes:
27
+ // 0 = annotation is well-formed, OR library is not yet installed (graceful degradation)
28
+ // 1 = annotation is malformed (library reports a conformance error)
29
+ // 2 = usage/argument error (bad MARKER, unreadable FILE, etc.)
30
+ //
31
+ // Integration notes:
32
+ // - When packages/protocol/ (issue #1291) is built and published, this script will
33
+ // work without any changes. The dynamic import path resolves relative to this script.
34
+ // - When forge-annotation.sh (issue #1267) is merged, its `validate` subcommand can
35
+ // shell out to this script for conformance checks: the Bash side handles sentinel
36
+ // and structure (AGPL, format rules), the Node side handles spec conformance (MIT).
37
+ // - Call-site wiring in commands/*.md is tracked separately in #1247.
38
+
39
+ import { readFileSync, existsSync } from 'node:fs';
40
+ import { createRequire } from 'node:module';
41
+ import { resolve, dirname } from 'node:path';
42
+ import { fileURLToPath, pathToFileURL } from 'node:url';
43
+
44
+ const SCRIPT_NAME = 'validate-annotation-node.mjs';
45
+ const __filename = fileURLToPath(import.meta.url);
46
+ const __dirname = dirname(__filename);
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // Usage
50
+ // ---------------------------------------------------------------------------
51
+ function usage() {
52
+ process.stderr.write(
53
+ `Usage:\n` +
54
+ ` ${SCRIPT_NAME} <MARKER> [FILE]\n` +
55
+ ` echo '<body>' | ${SCRIPT_NAME} <MARKER>\n` +
56
+ `\n` +
57
+ `Arguments:\n` +
58
+ ` MARKER Annotation type (e.g. INVESTIGATOR, FORGE:INVESTIGATOR, CONTRACT, BUILDER)\n` +
59
+ ` FILE File containing the annotation body (reads stdin if omitted or '-')\n` +
60
+ `\n` +
61
+ `Exit codes:\n` +
62
+ ` 0 Valid annotation, or library not yet installed (graceful degradation)\n` +
63
+ ` 1 Invalid annotation (library conformance check failed)\n` +
64
+ ` 2 Usage/argument error\n`
65
+ );
66
+ }
67
+
68
+ // ---------------------------------------------------------------------------
69
+ // Normalize marker: strip FORGE: prefix, uppercase
70
+ // Mirrors forge-annotation.sh normalize_marker() so both tools agree on marker names.
71
+ // ---------------------------------------------------------------------------
72
+ function normalizeMarker(raw) {
73
+ return raw.toUpperCase().replace(/^FORGE:/, '');
74
+ }
75
+
76
+ // ---------------------------------------------------------------------------
77
+ // Read body from file path or stdin
78
+ // ---------------------------------------------------------------------------
79
+ async function readBody(fileArg) {
80
+ if (!fileArg || fileArg === '-') {
81
+ // Read from stdin
82
+ return new Promise((resolve, reject) => {
83
+ const chunks = [];
84
+ process.stdin.on('data', (chunk) => chunks.push(chunk));
85
+ process.stdin.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
86
+ process.stdin.on('error', reject);
87
+ });
88
+ }
89
+
90
+ if (!existsSync(fileArg)) {
91
+ process.stderr.write(`ERROR: file not found: ${fileArg}\n`);
92
+ process.exit(2);
93
+ }
94
+
95
+ try {
96
+ return readFileSync(fileArg, 'utf8');
97
+ } catch (err) {
98
+ process.stderr.write(`ERROR: cannot read file '${fileArg}': ${err.message}\n`);
99
+ process.exit(2);
100
+ }
101
+ }
102
+
103
+ // ---------------------------------------------------------------------------
104
+ // Locate the protocol library
105
+ // Library expected at packages/protocol/src/index.js relative to the repo
106
+ // root (per packages/protocol/package.json main/exports).
107
+ // Repo root is two directories above this script (scripts/ → repo root).
108
+ // ---------------------------------------------------------------------------
109
+ function resolveLibraryPath() {
110
+ const repoRoot = resolve(__dirname, '..');
111
+ return resolve(repoRoot, 'packages', 'protocol', 'src', 'index.js');
112
+ }
113
+
114
+ // ---------------------------------------------------------------------------
115
+ // Main
116
+ // ---------------------------------------------------------------------------
117
+ async function main() {
118
+ const args = process.argv.slice(2);
119
+
120
+ if (args.length === 0 || args[0] === '-h' || args[0] === '--help') {
121
+ usage();
122
+ process.exit(args.length === 0 ? 2 : 0);
123
+ }
124
+
125
+ const markerRaw = args[0];
126
+ const fileArg = args[1] || null;
127
+ const marker = normalizeMarker(markerRaw);
128
+
129
+ if (!marker || !/^[A-Z_]+$/.test(marker)) {
130
+ process.stderr.write(`ERROR: invalid MARKER '${markerRaw}' — must be a non-empty string (letters and underscores only after prefix stripping)\n`);
131
+ usage();
132
+ process.exit(2);
133
+ }
134
+
135
+ const body = await readBody(fileArg);
136
+
137
+ if (!body.trim()) {
138
+ process.stderr.write(`ERROR: annotation body is empty — nothing to validate\n`);
139
+ process.exit(2);
140
+ }
141
+
142
+ // ---------------------------------------------------------------------------
143
+ // Attempt to load the protocol library (graceful degradation if absent)
144
+ // ---------------------------------------------------------------------------
145
+ const libraryPath = resolveLibraryPath();
146
+
147
+ if (!existsSync(libraryPath)) {
148
+ // Library not yet built (#1291 not merged) — exit 0 with a warning.
149
+ // This is the expected state until #1291 lands; the pipeline must not break.
150
+ process.stdout.write(
151
+ `WARN: forge-protocol library not found at packages/protocol/src/index.js\n` +
152
+ ` Conformance validation skipped — install the library from #1291 to enable spec-conformance checks.\n` +
153
+ ` Annotation posting will proceed without library validation.\n`
154
+ );
155
+ process.exit(0);
156
+ }
157
+
158
+ let library;
159
+ try {
160
+ // Dynamic import() requires a file:// URL for absolute paths on Windows
161
+ // (a bare "C:\..." path is misparsed as a URL with protocol "c:"). Using
162
+ // pathToFileURL() makes this work identically on Windows and POSIX.
163
+ library = await import(pathToFileURL(libraryPath));
164
+ } catch (err) {
165
+ // Library file exists but failed to load — log and degrade gracefully.
166
+ process.stdout.write(
167
+ `WARN: forge-protocol library found at packages/protocol/src/index.js but failed to load: ${err.message}\n` +
168
+ ` Conformance validation skipped. Check that the library is built correctly.\n`
169
+ );
170
+ process.exit(0);
171
+ }
172
+
173
+ // ---------------------------------------------------------------------------
174
+ // Parse the body into a structured annotation, then call library.validate().
175
+ // The library's validate() expects a ParsedAnnotation (the output of parse()),
176
+ // NOT a raw { type, body } literal — validate() reads annotation.isReserved,
177
+ // .fields, .sentinelState, etc., all of which only exist after parse() runs.
178
+ // Passing an unparsed literal makes validate() take its "unknown type, tolerate"
179
+ // early-return path (§7.2.4) and always report { valid: true }, silently
180
+ // skipping every real conformance check.
181
+ // ---------------------------------------------------------------------------
182
+ if (typeof library.parse !== 'function' || typeof library.validate !== 'function') {
183
+ process.stderr.write(
184
+ `ERROR: forge-protocol library does not export the expected 'parse' and 'validate' functions.\n` +
185
+ ` Found exports: ${Object.keys(library).join(', ') || '(none)'}\n`
186
+ );
187
+ process.exit(2);
188
+ }
189
+
190
+ let annotation;
191
+ try {
192
+ const parsed = library.parse(body);
193
+ annotation = parsed.find((a) => a.type === marker);
194
+ } catch (err) {
195
+ process.stderr.write(`ERROR: library.parse() threw an exception: ${err.message}\n`);
196
+ process.exit(1);
197
+ }
198
+
199
+ if (!annotation) {
200
+ process.stderr.write(
201
+ `ERROR: no '${marker}' annotation found in the provided body — expected an opening tag ` +
202
+ `like <!-- FORGE:${marker} --> somewhere in the input.\n`
203
+ );
204
+ process.exit(2);
205
+ }
206
+
207
+ let result;
208
+ try {
209
+ result = library.validate(annotation);
210
+ } catch (err) {
211
+ process.stderr.write(`ERROR: library.validate() threw an exception: ${err.message}\n`);
212
+ process.exit(1);
213
+ }
214
+
215
+ if (result && result.valid === true) {
216
+ process.stdout.write(`OK: '${marker}' annotation is well-formed (forge-protocol conformance check passed)\n`);
217
+ process.exit(0);
218
+ } else {
219
+ const errors = (result && Array.isArray(result.errors) && result.errors.length > 0)
220
+ ? result.errors.join('\n - ')
221
+ : 'no error details returned by library';
222
+ process.stderr.write(
223
+ `ERROR: '${marker}' annotation failed forge-protocol conformance check:\n` +
224
+ ` - ${errors}\n`
225
+ );
226
+ process.exit(1);
227
+ }
228
+ }
229
+
230
+ main().catch((err) => {
231
+ process.stderr.write(`FATAL: ${err.message}\n`);
232
+ process.exit(2);
233
+ });
@@ -135,10 +135,10 @@ case "$SUBCOMMAND" in
135
135
  exit 1
136
136
  fi
137
137
  echo "Branch '$branch' exists locally — attaching it at '$path' (preserving existing commits)..."
138
- git -C "$ANCHOR" worktree add "$path" "$branch"
138
+ git -C "$ANCHOR" worktree add -- "$path" "$branch"
139
139
  else
140
140
  echo "Creating new branch '$branch' from '$base_ref' at '$path'..."
141
- git -C "$ANCHOR" worktree add "$path" -b "$branch" "$base_ref"
141
+ git -C "$ANCHOR" worktree add -b "$branch" -- "$path" "$base_ref"
142
142
  fi
143
143
  echo "OK: worktree ready at '$path' on branch '$branch'"
144
144
  }
@@ -189,7 +189,7 @@ case "$SUBCOMMAND" in
189
189
  git -C "$ANCHOR" worktree remove "$WT_PATH" --force 2>/dev/null || true
190
190
 
191
191
  echo "Deleting local branch '$BRANCH' (if present)..."
192
- git -C "$ANCHOR" branch -D "$BRANCH" 2>/dev/null || true
192
+ git -C "$ANCHOR" branch -D -- "$BRANCH" 2>/dev/null || true
193
193
 
194
194
  echo "OK: cleanup complete for '$WT_PATH' / '$BRANCH'"
195
195
  ;;
@@ -228,6 +228,20 @@ When `forge.yaml → repos.satellites` is configured, issue numbers can be prefi
228
228
 
229
229
  ---
230
230
 
231
+ ## Spec Compliance Rules
232
+
233
+ Command specs declare constraints that agents MUST follow. These rules are not enforced at the tool-call level — compliance depends entirely on the agent reading and obeying the spec. Violations are invisible to the pipeline and can silently skip critical safety gates.
234
+
235
+ 1. **Respect `allowed-tools` frontmatter.** When a command spec declares `allowed-tools:` in its YAML frontmatter, NEVER use a tool not in that list. In particular, NEVER use the `Agent` tool when it is not listed — use `Task` or `Skill` instead. The `Agent` tool spawns opaque subprocesses that bypass `allowed-tools` constraints and cannot post structured output.
236
+
237
+ 2. **Execute Phase 0 routing before any other phase.** Commands with Phase 0 routing conditions (e.g., `review-pr.md` routing staging PRs to `review-pr-staging`) MUST evaluate the routing check before proceeding. Skipping routing can bypass entire sub-pipelines including deploy gates, test gates, and structured finding creation.
238
+
239
+ 3. **Look for `FORGE:SPEC_LOADED` markers.** Critical command specs include a `<!-- FORGE:SPEC_LOADED -->` HTML comment near the top. If you are executing a command and do not see this marker in your context, the spec was not loaded — stop and re-read the command file before proceeding.
240
+
241
+ <!-- Added: forge#1383 — agent spec-compliance enforcement -->
242
+
243
+ ---
244
+
231
245
  ## Idempotency Rules
232
246
 
233
247
  - Re-invoking `/work-on` on a completed issue is safe — it reads the terminal label and stops
@@ -1,85 +0,0 @@
1
- import { defineConfig } from 'vitepress'
2
-
3
- export default defineConfig({
4
- title: 'ForgeDock',
5
- description: 'Autonomous AI development pipeline that uses GitHub as a structured knowledge graph for Claude Code agents.',
6
- lang: 'en-US',
7
-
8
- // Deploy to GitHub Pages at /
9
- base: '/',
10
-
11
- head: [
12
- ['link', { rel: 'icon', href: '/favicon.ico' }],
13
- ['meta', { name: 'theme-color', content: '#7C3AED' }],
14
- ['meta', { property: 'og:type', content: 'website' }],
15
- ['meta', { property: 'og:site_name', content: 'ForgeDock' }],
16
- ],
17
-
18
- themeConfig: {
19
- logo: 'https://avatars.githubusercontent.com/in/3731547?s=40',
20
- siteTitle: 'ForgeDock',
21
-
22
- nav: [
23
- { text: 'Quick Start', link: '/getting-started' },
24
- { text: 'Commands', link: '/command-reference' },
25
- { text: 'Annotations', link: '/annotations-explained' },
26
- {
27
- text: 'GitHub',
28
- link: 'https://github.com/RapierCraftStudios/ForgeDock',
29
- },
30
- ],
31
-
32
- sidebar: [
33
- {
34
- text: 'Get Started',
35
- items: [
36
- { text: 'Quick Start', link: '/getting-started' },
37
- { text: 'How It Works', link: '/how-it-works' },
38
- { text: 'Troubleshooting', link: '/troubleshooting' },
39
- ],
40
- },
41
- {
42
- text: 'Reference',
43
- items: [
44
- { text: 'Command Reference', link: '/command-reference' },
45
- { text: 'Command Learning Path', link: '/command-learning-path' },
46
- { text: 'FORGE Annotation Protocol', link: '/forge-annotation-protocol' },
47
- { text: 'Annotations Explained', link: '/annotations-explained' },
48
- ],
49
- },
50
- {
51
- text: 'Guides',
52
- items: [
53
- { text: 'ForgeDock vs. Manual Workflows', link: '/vs-manual-workflows' },
54
- ],
55
- },
56
- ],
57
-
58
- socialLinks: [
59
- { icon: 'github', link: 'https://github.com/RapierCraftStudios/ForgeDock' },
60
- { icon: 'npm', link: 'https://www.npmjs.com/package/forgedock' },
61
- ],
62
-
63
- footer: {
64
- message: 'Released under the AGPL-3.0 License.',
65
- copyright: 'Copyright © 2024–2026 RapierCraft Studios',
66
- },
67
-
68
- editLink: {
69
- pattern: 'https://github.com/RapierCraftStudios/ForgeDock/edit/main/docs/site/:path',
70
- text: 'Edit this page on GitHub',
71
- },
72
-
73
- search: {
74
- provider: 'local',
75
- },
76
-
77
- outline: {
78
- level: [2, 3],
79
- },
80
- },
81
-
82
- markdown: {
83
- lineNumbers: false,
84
- },
85
- })