forgedock 1.1.1 → 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.
- package/README.md +53 -8
- package/bin/batch-runner.mjs +339 -0
- package/bin/demo.mjs +92 -7
- package/bin/engine/phases.mjs +20 -0
- package/bin/engine/state.mjs +8 -3
- package/bin/engine-cli.mjs +177 -5
- package/bin/forgedock.mjs +409 -26
- package/bin/hooks/interactive-engine.mjs +509 -0
- package/bin/hooks/pre-tool-use.mjs +498 -0
- package/bin/hooks/subagent-stop-enforce.mjs +197 -0
- package/bin/journey.mjs +72 -14
- package/bin/registry.mjs +139 -0
- package/bin/report.mjs +447 -0
- package/bin/runner.mjs +68 -19
- package/bin/settings-hook.mjs +235 -1
- package/commands/adopt.md +4 -1
- package/commands/analytics.md +4 -1
- package/commands/audit-agents.md +110 -27
- package/commands/audit.md +20 -1
- package/commands/autopilot.md +80 -8
- package/commands/changelog.md +2 -1
- package/commands/ci-audit.md +2 -1
- package/commands/cleanup.md +161 -1
- package/commands/compat-audit.md +2 -1
- package/commands/deploy-info.md +58 -1
- package/commands/design-bench.md +5 -1
- package/commands/design-render-critique-loop.md +1 -1
- package/commands/diagnose.md +2 -1
- package/commands/explain.md +2 -1
- package/commands/forge-stats.md +121 -2
- package/commands/forgedock-init.md +1 -1
- package/commands/geo-audit.md +3 -0
- package/commands/incident-response.md +2 -1
- package/commands/issue.md +23 -2
- package/commands/milestone.md +77 -12
- package/commands/optimize.md +2 -1
- package/commands/orchestrate/config.md +67 -0
- package/commands/orchestrate/phase-1-resolve.md +159 -0
- package/commands/orchestrate/phase-2-triage.md +126 -0
- package/commands/orchestrate/phase-2.5-synthesis.md +156 -0
- package/commands/orchestrate/phase-3-dependency.md +591 -0
- package/commands/orchestrate/phase-4-execution.md +962 -0
- package/commands/orchestrate/phase-5-cleanup.md +47 -0
- package/commands/orchestrate/phase-6-report.md +198 -0
- package/commands/orchestrate/safety.md +62 -0
- package/commands/orchestrate.md +25 -2045
- package/commands/pipeline-health/config.md +34 -0
- package/commands/pipeline-health/phase-1-context.md +180 -0
- package/commands/pipeline-health/phase-2-metrics.md +1729 -0
- package/commands/pipeline-health/phase-3-analyze.md +204 -0
- package/commands/pipeline-health/phase-4-proposals.md +117 -0
- package/commands/pipeline-health/phase-5-report.md +284 -0
- package/commands/pipeline-health/phase-6-summary.md +52 -0
- package/commands/pipeline-health.md +25 -2401
- package/commands/pipeline-resume.md +24 -1
- package/commands/pipeline-status.md +65 -0
- package/commands/qa-sweep.md +4 -1
- package/commands/quality-gate.md +248 -10
- package/commands/replay.md +2 -1
- package/commands/review-pr-agents/api.md +135 -0
- package/commands/review-pr-agents/auth.md +137 -0
- package/commands/review-pr-agents/billing.md +101 -0
- package/commands/review-pr-agents/concurrency.md +145 -0
- package/commands/review-pr-agents/database.md +103 -0
- package/commands/review-pr-agents/frontend.md +89 -0
- package/commands/review-pr-agents/infra.md +385 -0
- package/commands/review-pr-agents/protocols.md +185 -0
- package/commands/review-pr-agents/scraper.md +185 -0
- package/commands/review-pr-agents/security.md +406 -0
- package/commands/review-pr-agents.md +45 -1722
- package/commands/review-pr-staging.md +196 -40
- package/commands/review-pr.md +591 -52
- package/commands/rollback.md +2 -1
- package/commands/scope.md +2 -1
- package/commands/security-audit.md +2 -1
- package/commands/signal-planner.md +623 -0
- package/commands/sync-ecosystem.md +1 -0
- package/commands/test-gate.md +17 -1
- package/commands/validate.md +2 -1
- package/commands/work-on/build/architect.md +28 -0
- package/commands/work-on/build/context.md +2 -0
- package/commands/work-on/build/implement.md +3 -1
- package/commands/work-on/build/validate.md +26 -7
- package/commands/work-on/build.md +163 -24
- package/commands/work-on/close.md +130 -3
- package/commands/work-on/decompose.md +19 -1
- package/commands/work-on/investigate.md +117 -2
- package/commands/work-on/review.md +24 -4
- package/commands/work-on-monolithic.md +7 -2
- package/commands/work-on.md +202 -4
- package/docs/BRAND.md +14 -7
- package/docs/CI.md +2 -2
- package/docs/CODEX.md +36 -1
- package/docs/CONFIG.md +5 -8
- package/docs/FORGE-PROTOCOL.md +15 -1064
- package/docs/anthropic-devrel-outreach.md +2 -2
- package/docs/articles/command-decomposition-token-savings.md +214 -0
- package/docs/articles/model-release-playbook.md +131 -0
- package/docs/articles/per-repo-adaptive-scripts-token-savings.md +27 -25
- package/docs/claude-breakpoints.json +75 -5
- package/docs/design/abc-benchmark.md +1 -1
- package/docs/design/design-spec-schema.md +1 -1
- package/docs/design/divergent-generation.md +1 -1
- package/docs/design/eval-gate-runbook.md +178 -0
- package/docs/design/show-hn-draft.md +26 -1
- package/docs/design/token-cost-baseline-1298.md +59 -0
- package/docs/eval/README.md +117 -0
- package/docs/eval/corpus/seed-v1.json +6 -0
- package/docs/eval/published/claude-sonnet-4-6-2026-07-04.json +33 -0
- package/docs/site/annotations-explained.md +24 -0
- package/docs/site/command-learning-path.md +51 -14
- package/docs/site/command-reference.md +339 -113
- package/docs/site/for-companies.md +95 -0
- package/docs/site/forge-annotation-protocol.md +12 -256
- package/docs/site/getting-started.md +43 -4
- package/docs/site/github-is-the-memory.md +113 -0
- package/docs/site/how-it-works.md +3 -13
- package/docs/site/index.md +9 -1
- package/docs/spec/eval-run-result.md +147 -0
- package/docs/spec/forge-protocol-v1.md +441 -18
- package/docs/spec/label-state-machine.md +55 -0
- package/docs/spec/review-protocol.md +158 -0
- package/docs/spec-graph-schema.md +28 -0
- package/docs/superpowers/specs/2026-07-04-economic-self-governance-design.md +181 -0
- package/docs/superpowers/specs/2026-07-04-provenance-trail-design.md +189 -0
- package/examples/forgedock-demo/README.md +41 -22
- package/examples/forgedock-demo/issues/06-bug-tags-type-validation.md +43 -0
- package/examples/forgedock-demo/issues/07-bug-negative-offset-pagination.md +42 -0
- package/examples/forgedock-demo/issues/08-bug-patch-mass-assignment.md +49 -0
- package/examples/forgedock-demo/issues/09-bug-count-injection.md +47 -0
- package/examples/forgedock-demo/issues/10-feature-sort-param.md +39 -0
- package/examples/forgedock-demo/issues/11-feature-patch-tags.md +37 -0
- package/examples/forgedock-demo/issues/12-feature-bulk-create.md +42 -0
- package/examples/forgedock-demo/issues/13-feature-archive-notes.md +46 -0
- package/examples/forgedock-demo/issues/14-refactor-extract-title-validation.md +41 -0
- package/examples/forgedock-demo/issues/15-refactor-protected-route-flag.md +43 -0
- package/examples/forgedock-demo/issues/16-refactor-extract-store.md +45 -0
- package/examples/forgedock-demo/issues/17-perf-tags-dedup-set.md +35 -0
- package/examples/forgedock-demo/issues/18-perf-remove-redundant-sort.md +37 -0
- package/examples/forgedock-demo/issues/19-docs-api-reference-full.md +53 -0
- package/examples/forgedock-demo/issues/20-docs-intentional-flaw-convention.md +46 -0
- package/examples/forgedock-demo/scripts/smoke.js +18 -1
- package/examples/forgedock-demo/src/db.js +36 -11
- package/examples/forgedock-demo/src/routes/notes.js +106 -9
- package/examples/forgedock-demo/src/server.js +4 -1
- package/package.json +3 -1
- package/packages/protocol/LICENSE +21 -0
- package/packages/protocol/src/cli.js +135 -0
- package/packages/protocol/src/emit.js +146 -0
- package/packages/protocol/src/index.js +43 -0
- package/packages/protocol/src/parse.js +229 -0
- package/packages/protocol/src/types.js +171 -0
- package/packages/protocol/src/validate.js +122 -0
- package/scripts/bench-topology-cost.mjs +372 -0
- package/scripts/check-command-docs-drift.sh +198 -0
- package/scripts/check-protocol-restatements.sh +176 -0
- package/scripts/check-registry/README.md +72 -0
- package/scripts/check-registry/manifest.json +6 -0
- package/scripts/code-index.sh +454 -0
- package/scripts/conformance-check.mjs +168 -0
- package/scripts/doctor-pipeline-state.sh +484 -0
- package/scripts/eval-gate-baseline.json +17 -0
- package/scripts/eval-gate-scorecard.mjs +242 -0
- package/scripts/eval-scorecard-aggregator.mjs +252 -0
- package/scripts/flaky-quarantine.sh +241 -0
- package/scripts/forge-annotation.sh +567 -0
- package/scripts/issue-dedup.sh +236 -0
- package/scripts/validate-annotation-node.mjs +233 -0
- package/scripts/worktree-lifecycle.sh +3 -3
- package/templates/devdocs/agent/using-forgedock.md +14 -0
- 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 "$
|
|
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
|
-
})
|