forgedock 1.5.0 → 1.6.0
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/bin/forgedock.mjs +72 -25
- package/bin/hooks/session-start.mjs +22 -1
- package/bin/journey.mjs +460 -399
- package/bin/registry.mjs +86 -0
- package/bin/runner.mjs +197 -34
- package/commands/orchestrate/phase-3-dependency.md +6 -1
- package/commands/orchestrate/phase-4-execution.md +113 -3
- package/commands/work-on/build/architect.md +28 -3
- package/package.json +1 -1
- package/scripts/build-knowledge-index.mjs +6 -2
- package/scripts/check-spec-markers.sh +1 -0
- package/scripts/derive-finding-milestone.sh +18 -8
|
@@ -29,7 +29,7 @@ COMPLEXITY_BAND=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
|
29
29
|
COMPLEXITY_BAND="${COMPLEXITY_BAND:-STANDARD}"
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
**If COMPLEXITY_BAND: TRIVIAL** → skip all phases (A0 through A5), post
|
|
32
|
+
**If COMPLEXITY_BAND: TRIVIAL** → skip all phases (A0 through A5), **post the minimal skip marker comment described in "Skip Marker" under Skip Conditions below** (reason: `TRIVIAL complexity band`), then return an empty plan to caller immediately. Do not read any files. This is not an error — trivial single-file changes have no cross-path consistency risk. The skip marker (ending in `<!-- FORGE:ARCHITECT:COMPLETE -->`) is what makes a legitimate skip observable to the headless engine's marker-only gate, so the issue advances to build instead of being stranded at `needs-human`. <!-- Added: forge#679; skip-marker on skip: forge#2689 -->
|
|
33
33
|
|
|
34
34
|
**If COMPLEXITY_BAND: STANDARD or COMPLEX** → proceed to Phase A0 below.
|
|
35
35
|
|
|
@@ -668,14 +668,39 @@ gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:ARCHITECT -->
|
|
|
668
668
|
|
|
669
669
|
## Skip Conditions
|
|
670
670
|
|
|
671
|
-
Skip
|
|
671
|
+
Skip the planning work (Phases A0–A5) and return an empty plan to caller — **but always post the Skip Marker below first** — if:
|
|
672
672
|
- **COMPLEXITY_BAND: TRIVIAL** — checked via FORGE:FAST_PATH comment at entry (see guard above) <!-- Primary skip path: forge#679 -->
|
|
673
673
|
- Issue creates only **new files** with no callers to find (e.g. a new command file with no existing integration point yet)
|
|
674
674
|
- Issue is a 1-file config or docs edit with no code logic
|
|
675
675
|
- Issue title starts with "docs:" or "chore:"
|
|
676
676
|
- `{AFFECTED_FILES}` is empty
|
|
677
677
|
|
|
678
|
-
When skipped, the builder proceeds with investigation report + context briefing only.
|
|
678
|
+
When skipped, the builder proceeds with investigation report + context briefing only (see Integration Point below — the builder falls back gracefully on an empty plan).
|
|
679
|
+
|
|
680
|
+
### Skip Marker (MANDATORY on every skip — do NOT post nothing) <!-- forge#2689 -->
|
|
681
|
+
|
|
682
|
+
**Do not post nothing.** The headless engine gate (`bin/engine/phases.mjs` → `architect.detectOutcome`) treats a **missing** `FORGE:ARCHITECT:COMPLETE` sentinel as a phase **failure**, retries to `maxAttempts` (3), then strands the issue at `needs-human`. It reads GitHub comment markers only — it cannot see this subcommand's in-process "empty plan" return value. So a skip that posts no marker is indistinguishable from a crashed architect run and deterministically strands **every** `chore:`/`docs:`/trivial issue.
|
|
683
|
+
|
|
684
|
+
On **every** skip branch above, post exactly this minimal comment (substitute the concrete `{SKIP_REASON}`, e.g. `chore:/docs: title`, `TRIVIAL complexity band`, `new files only — no callers`, or `AFFECTED_FILES empty`) **before** returning the empty plan:
|
|
685
|
+
|
|
686
|
+
```bash
|
|
687
|
+
# The skip marker is mandatory and un-gated by design — posting it IS the point
|
|
688
|
+
# (a missing marker strands the issue at needs-human; see above). Same report-only
|
|
689
|
+
# risk class as the unguarded plan post in "Output Format" above, so it carries the
|
|
690
|
+
# same allowlist rather than a DRY_RUN guard.
|
|
691
|
+
SKIP_BODY=$(cat <<SKIP_EOF
|
|
692
|
+
<!-- FORGE:ARCHITECT -->
|
|
693
|
+
## Architecture Plan — Skipped
|
|
694
|
+
|
|
695
|
+
**Skipped**: {SKIP_REASON}. No cross-path consistency risk — the builder proceeds with the investigation report + context briefing.
|
|
696
|
+
|
|
697
|
+
<!-- FORGE:ARCHITECT:COMPLETE -->
|
|
698
|
+
SKIP_EOF
|
|
699
|
+
)
|
|
700
|
+
gh issue comment {NUMBER} {GH_FLAG} --body "$SKIP_BODY" # <!-- allowlist:check-command-side-effects -->
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
The empty-plan-with-marker changes no downstream behavior — the builder already falls back to the investigation report + contract when the plan is empty (see Integration Point below). The marker only makes the legitimate skip **observable** to the marker-only engine gate. This intentionally does **not** weaken crash detection: a genuinely interrupted architect run still posts no marker at all, so `detectOutcome` still fails-and-escalates it correctly.
|
|
679
704
|
|
|
680
705
|
---
|
|
681
706
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "forgedock",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
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"
|
|
@@ -48,7 +48,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync, appendFileSync, rea
|
|
|
48
48
|
import { execSync, spawnSync } from 'child_process';
|
|
49
49
|
import { homedir } from 'os';
|
|
50
50
|
import { dirname, join, resolve } from 'path';
|
|
51
|
-
import { fileURLToPath } from 'url';
|
|
51
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
52
52
|
|
|
53
53
|
const __filename = fileURLToPath(import.meta.url);
|
|
54
54
|
const __dirname = dirname(__filename);
|
|
@@ -309,7 +309,11 @@ async function getParser() {
|
|
|
309
309
|
const parsePath = join(repoRoot, 'packages', 'protocol', 'src', 'parse.js');
|
|
310
310
|
|
|
311
311
|
try {
|
|
312
|
-
|
|
312
|
+
// On win32 a bare absolute path (C:\...) makes Node parse the drive letter as a
|
|
313
|
+
// URL scheme (ERR_UNSUPPORTED_ESM_URL_SCHEME). Convert to a file:// URL first —
|
|
314
|
+
// valid on POSIX too, and matches the import(pathToFileURL(...).href) idiom used
|
|
315
|
+
// at check-admission-parity.mjs / validate-annotation-node.mjs / check-phase-registry-drift.mjs.
|
|
316
|
+
const mod = await import(pathToFileURL(parsePath).href);
|
|
313
317
|
_parseAnnotation = mod.parse;
|
|
314
318
|
return _parseAnnotation;
|
|
315
319
|
} catch (e) {
|
|
@@ -126,11 +126,15 @@ fi
|
|
|
126
126
|
# Portable (non-PCRE) extraction — grep -oP is not supported by Git Bash's
|
|
127
127
|
# grep build on Windows, so use a bash regex + BASH_REMATCH instead of a
|
|
128
128
|
# lookbehind (same convention as scripts/code-index.sh).
|
|
129
|
+
# The leading (^|[^a-z]) group anchors the alternation to a word start so a
|
|
130
|
+
# close-verb embedded in a larger word (e.g. "encloses #1234") cannot match;
|
|
131
|
+
# the body is already lowercased, so [^a-z] is the correct complement class.
|
|
132
|
+
# The extra group shifts the issue-number capture to BASH_REMATCH[3].
|
|
129
133
|
PR_BODY=$(echo "$PR_JSON" | jq -r '.body // empty')
|
|
130
134
|
SOURCE_ISSUE=""
|
|
131
135
|
PR_BODY_LOWER=$(echo "$PR_BODY" | tr '[:upper:]' '[:lower:]')
|
|
132
|
-
if [[ "$PR_BODY_LOWER" =~ (closes|fixes|resolves)[[:space:]]*#([0-9]+) ]]; then
|
|
133
|
-
SOURCE_ISSUE="${BASH_REMATCH[
|
|
136
|
+
if [[ "$PR_BODY_LOWER" =~ (^|[^a-z])(closes|fixes|resolves)[[:space:]]*#([0-9]+) ]]; then
|
|
137
|
+
SOURCE_ISSUE="${BASH_REMATCH[3]}"
|
|
134
138
|
fi
|
|
135
139
|
|
|
136
140
|
if [ -n "$SOURCE_ISSUE" ]; then
|
|
@@ -167,12 +171,18 @@ if [ -n "$MILESTONE_BRANCH" ]; then
|
|
|
167
171
|
ALL_MILESTONES=$(gh api "repos/${MILESTONES_REPO}/milestones" 2>/dev/null || echo "")
|
|
168
172
|
|
|
169
173
|
if [ -n "$ALL_MILESTONES" ] && [ "$ALL_MILESTONES" != "[]" ]; then
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
174
|
+
# Single jq invocation: shared `def slugify` replaces the duplicated
|
|
175
|
+
# downcase/gsub slug transform that previously appeared once per tier
|
|
176
|
+
# (exact-match, then substring-fallback). Exact match is tried first
|
|
177
|
+
# (array 1), substring match second (array 2); `first` picks the winner
|
|
178
|
+
# from the concatenation, preserving the exact-match-wins-over-substring
|
|
179
|
+
# ordering and the regex semantics of `test($slug)` exactly as before.
|
|
180
|
+
# <!-- Fixes: forge#2670 -->
|
|
181
|
+
MATCH=$(echo "$ALL_MILESTONES" | jq -r --arg slug "$BRANCH_SLUG" '
|
|
182
|
+
def slugify: ascii_downcase | gsub("[^a-z0-9]+"; "-");
|
|
183
|
+
([.[] | select((.title | slugify) == $slug)]
|
|
184
|
+
+ [.[] | select((.title | slugify) | test($slug))])
|
|
185
|
+
| (first.title // empty)')
|
|
176
186
|
if [ -n "$MATCH" ]; then
|
|
177
187
|
echo "$MATCH"
|
|
178
188
|
exit 0
|