@garygentry/feature-forge 0.2.3 → 0.2.5
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 +6 -1
- package/adapters/GENERATION-REPORT.md +5 -1
- package/adapters/claude/.feature-forge-bundle.json +1 -1
- package/adapters/claude/references/forge-config-schema.json +25 -3
- package/adapters/claude/references/pipeline-state-schema.json +3 -2
- package/adapters/claude/references/portable-root.md +6 -3
- package/adapters/claude/references/process-overview.md +10 -0
- package/adapters/claude/references/shared-conventions.md +27 -9
- package/adapters/claude/references/stage-exit-protocol.md +206 -0
- package/adapters/claude/scripts/epic-manifest.py +10 -0
- package/adapters/claude/scripts/forge-bootstrap.py +94 -16
- package/adapters/claude/scripts/forge-init.sh +7 -1
- package/adapters/claude/scripts/forge-root.sh +20 -1
- package/adapters/claude/scripts/forge-session.py +866 -31
- package/adapters/claude/skills/forge/SKILL.md +29 -15
- package/adapters/claude/skills/forge-0-epic/SKILL.md +19 -24
- package/adapters/claude/skills/forge-0-epic/references/edit-mode.md +6 -4
- package/adapters/claude/skills/forge-0-epic/references/epic-manifest-subcommands.md +1 -1
- package/adapters/claude/skills/forge-1-prd/SKILL.md +13 -4
- package/adapters/claude/skills/forge-2-tech/SKILL.md +13 -3
- package/adapters/claude/skills/forge-3-specs/SKILL.md +13 -3
- package/adapters/claude/skills/forge-4-backlog/SKILL.md +15 -5
- package/adapters/claude/skills/forge-5-loop/SKILL.md +19 -21
- package/adapters/claude/skills/forge-5-loop/references/result-reporting.md +10 -5
- package/adapters/claude/skills/forge-6-docs/SKILL.md +6 -6
- package/adapters/claude/skills/forge-bootstrap/SKILL.md +4 -4
- package/adapters/claude/skills/forge-fix/SKILL.md +29 -6
- package/adapters/claude/skills/forge-guide/SKILL.md +182 -0
- package/adapters/claude/skills/forge-init/SKILL.md +29 -1
- package/adapters/claude/skills/forge-verify/SKILL.md +46 -15
- package/adapters/claude/skills/forge-verify/references/verification-checklists.md +1 -1
- package/adapters/codex/.feature-forge-bundle.json +1 -1
- package/adapters/codex/references/forge-config-schema.json +25 -3
- package/adapters/codex/references/pipeline-state-schema.json +3 -2
- package/adapters/codex/references/portable-root.md +6 -3
- package/adapters/codex/references/process-overview.md +10 -0
- package/adapters/codex/references/shared-conventions.md +27 -9
- package/adapters/codex/references/stage-exit-protocol.md +206 -0
- package/adapters/codex/scripts/epic-manifest.py +10 -0
- package/adapters/codex/scripts/forge-bootstrap.py +94 -16
- package/adapters/codex/scripts/forge-init.sh +7 -1
- package/adapters/codex/scripts/forge-root.sh +20 -1
- package/adapters/codex/scripts/forge-session.py +866 -31
- package/adapters/codex/skills/forge/SKILL.md +34 -20
- package/adapters/codex/skills/forge-0-epic/SKILL.md +20 -25
- package/adapters/codex/skills/forge-0-epic/references/edit-mode.md +6 -4
- package/adapters/codex/skills/forge-0-epic/references/epic-manifest-subcommands.md +1 -1
- package/adapters/codex/skills/forge-1-prd/SKILL.md +13 -4
- package/adapters/codex/skills/forge-2-tech/SKILL.md +14 -4
- package/adapters/codex/skills/forge-3-specs/SKILL.md +13 -3
- package/adapters/codex/skills/forge-4-backlog/SKILL.md +15 -5
- package/adapters/codex/skills/forge-5-loop/SKILL.md +21 -23
- package/adapters/codex/skills/forge-5-loop/references/result-reporting.md +10 -5
- package/adapters/codex/skills/forge-6-docs/SKILL.md +6 -6
- package/adapters/codex/skills/forge-bootstrap/SKILL.md +4 -4
- package/adapters/codex/skills/forge-fix/SKILL.md +29 -6
- package/adapters/codex/skills/forge-guide/SKILL.md +191 -0
- package/adapters/codex/skills/forge-init/SKILL.md +29 -1
- package/adapters/codex/skills/forge-verify/SKILL.md +45 -14
- package/adapters/codex/skills/forge-verify/references/verification-checklists.md +1 -1
- package/adapters/copilot/.feature-forge-bundle.json +1 -1
- package/adapters/copilot/references/forge-config-schema.json +25 -3
- package/adapters/copilot/references/pipeline-state-schema.json +3 -2
- package/adapters/copilot/references/portable-root.md +6 -3
- package/adapters/copilot/references/process-overview.md +10 -0
- package/adapters/copilot/references/shared-conventions.md +27 -9
- package/adapters/copilot/references/stage-exit-protocol.md +206 -0
- package/adapters/copilot/scripts/epic-manifest.py +10 -0
- package/adapters/copilot/scripts/forge-bootstrap.py +94 -16
- package/adapters/copilot/scripts/forge-init.sh +7 -1
- package/adapters/copilot/scripts/forge-root.sh +20 -1
- package/adapters/copilot/scripts/forge-session.py +866 -31
- package/adapters/copilot/skills/forge/forge.md +34 -20
- package/adapters/copilot/skills/forge-0-epic/forge-0-epic.md +20 -25
- package/adapters/copilot/skills/forge-0-epic/references/edit-mode.md +6 -4
- package/adapters/copilot/skills/forge-0-epic/references/epic-manifest-subcommands.md +1 -1
- package/adapters/copilot/skills/forge-1-prd/forge-1-prd.md +13 -4
- package/adapters/copilot/skills/forge-2-tech/forge-2-tech.md +14 -4
- package/adapters/copilot/skills/forge-3-specs/forge-3-specs.md +13 -3
- package/adapters/copilot/skills/forge-4-backlog/forge-4-backlog.md +15 -5
- package/adapters/copilot/skills/forge-5-loop/forge-5-loop.md +21 -23
- package/adapters/copilot/skills/forge-5-loop/references/result-reporting.md +10 -5
- package/adapters/copilot/skills/forge-6-docs/forge-6-docs.md +6 -6
- package/adapters/copilot/skills/forge-bootstrap/forge-bootstrap.md +4 -4
- package/adapters/copilot/skills/forge-fix/forge-fix.md +29 -6
- package/adapters/copilot/skills/forge-guide/forge-guide.md +191 -0
- package/adapters/copilot/skills/forge-init/forge-init.md +29 -1
- package/adapters/copilot/skills/forge-verify/forge-verify.md +45 -14
- package/adapters/copilot/skills/forge-verify/references/verification-checklists.md +1 -1
- package/adapters/cursor/.feature-forge-bundle.json +1 -1
- package/adapters/cursor/references/forge-config-schema.json +25 -3
- package/adapters/cursor/references/pipeline-state-schema.json +3 -2
- package/adapters/cursor/references/portable-root.md +6 -3
- package/adapters/cursor/references/process-overview.md +10 -0
- package/adapters/cursor/references/shared-conventions.md +27 -9
- package/adapters/cursor/references/stage-exit-protocol.md +206 -0
- package/adapters/cursor/scripts/epic-manifest.py +10 -0
- package/adapters/cursor/scripts/forge-bootstrap.py +94 -16
- package/adapters/cursor/scripts/forge-init.sh +7 -1
- package/adapters/cursor/scripts/forge-root.sh +20 -1
- package/adapters/cursor/scripts/forge-session.py +866 -31
- package/adapters/cursor/skills/forge/forge.mdc +34 -20
- package/adapters/cursor/skills/forge-0-epic/forge-0-epic.mdc +20 -25
- package/adapters/cursor/skills/forge-0-epic/references/edit-mode.md +6 -4
- package/adapters/cursor/skills/forge-0-epic/references/epic-manifest-subcommands.md +1 -1
- package/adapters/cursor/skills/forge-1-prd/forge-1-prd.mdc +13 -4
- package/adapters/cursor/skills/forge-2-tech/forge-2-tech.mdc +14 -4
- package/adapters/cursor/skills/forge-3-specs/forge-3-specs.mdc +13 -3
- package/adapters/cursor/skills/forge-4-backlog/forge-4-backlog.mdc +15 -5
- package/adapters/cursor/skills/forge-5-loop/forge-5-loop.mdc +21 -23
- package/adapters/cursor/skills/forge-5-loop/references/result-reporting.md +10 -5
- package/adapters/cursor/skills/forge-6-docs/forge-6-docs.mdc +6 -6
- package/adapters/cursor/skills/forge-bootstrap/forge-bootstrap.mdc +4 -4
- package/adapters/cursor/skills/forge-fix/forge-fix.mdc +29 -6
- package/adapters/cursor/skills/forge-guide/forge-guide.mdc +192 -0
- package/adapters/cursor/skills/forge-init/forge-init.mdc +29 -1
- package/adapters/cursor/skills/forge-verify/forge-verify.mdc +45 -14
- package/adapters/cursor/skills/forge-verify/references/verification-checklists.md +1 -1
- package/adapters/gemini/.feature-forge-bundle.json +1 -1
- package/adapters/gemini/gemini-extension.json +5 -1
- package/adapters/gemini/references/forge-config-schema.json +25 -3
- package/adapters/gemini/references/pipeline-state-schema.json +3 -2
- package/adapters/gemini/references/portable-root.md +6 -3
- package/adapters/gemini/references/process-overview.md +10 -0
- package/adapters/gemini/references/shared-conventions.md +27 -9
- package/adapters/gemini/references/stage-exit-protocol.md +206 -0
- package/adapters/gemini/scripts/epic-manifest.py +10 -0
- package/adapters/gemini/scripts/forge-bootstrap.py +94 -16
- package/adapters/gemini/scripts/forge-init.sh +7 -1
- package/adapters/gemini/scripts/forge-root.sh +20 -1
- package/adapters/gemini/scripts/forge-session.py +866 -31
- package/adapters/gemini/skills/forge/forge.md +34 -20
- package/adapters/gemini/skills/forge-0-epic/forge-0-epic.md +20 -25
- package/adapters/gemini/skills/forge-0-epic/references/edit-mode.md +6 -4
- package/adapters/gemini/skills/forge-0-epic/references/epic-manifest-subcommands.md +1 -1
- package/adapters/gemini/skills/forge-1-prd/forge-1-prd.md +13 -4
- package/adapters/gemini/skills/forge-2-tech/forge-2-tech.md +14 -4
- package/adapters/gemini/skills/forge-3-specs/forge-3-specs.md +13 -3
- package/adapters/gemini/skills/forge-4-backlog/forge-4-backlog.md +15 -5
- package/adapters/gemini/skills/forge-5-loop/forge-5-loop.md +21 -23
- package/adapters/gemini/skills/forge-5-loop/references/result-reporting.md +10 -5
- package/adapters/gemini/skills/forge-6-docs/forge-6-docs.md +6 -6
- package/adapters/gemini/skills/forge-bootstrap/forge-bootstrap.md +4 -4
- package/adapters/gemini/skills/forge-fix/forge-fix.md +29 -6
- package/adapters/gemini/skills/forge-guide/forge-guide.md +191 -0
- package/adapters/gemini/skills/forge-init/forge-init.md +29 -1
- package/adapters/gemini/skills/forge-verify/forge-verify.md +45 -14
- package/adapters/gemini/skills/forge-verify/references/verification-checklists.md +1 -1
- package/dist/apply.js +34 -8
- package/dist/cli.js +40 -4
- package/dist/fsutil.d.ts +0 -12
- package/dist/fsutil.js +10 -1
- package/dist/manifest.d.ts +1 -1
- package/dist/plan.js +22 -2
- package/dist/rauf.d.ts +4 -4
- package/dist/rauf.js +3 -3
- package/dist/report.js +1 -1
- package/dist/types.d.ts +1 -1
- package/package.json +1 -1
|
@@ -26,6 +26,8 @@ import argparse
|
|
|
26
26
|
import json
|
|
27
27
|
import os
|
|
28
28
|
import re
|
|
29
|
+
import shlex
|
|
30
|
+
import shutil
|
|
29
31
|
import subprocess
|
|
30
32
|
import sys
|
|
31
33
|
import tempfile
|
|
@@ -281,6 +283,32 @@ def _json_text(obj: object) -> str:
|
|
|
281
283
|
return json.dumps(obj, indent=2, ensure_ascii=False) + "\n"
|
|
282
284
|
|
|
283
285
|
|
|
286
|
+
def contained_path(base: Path, *parts: str) -> Path:
|
|
287
|
+
"""Join ``parts`` onto ``base`` and assert the result stays within ``base``.
|
|
288
|
+
|
|
289
|
+
Ports epic-manifest.py's ``contained_path`` guard: canonicalizes (symlink-
|
|
290
|
+
resolves) both ends and verifies containment so no scaffold write or verify
|
|
291
|
+
cwd can escape the target repo — defense in depth behind ``_validate_answers``
|
|
292
|
+
should a crafted member path slip through. Containment violations surface only
|
|
293
|
+
as exit-2 usage errors.
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
base: The containing directory (the target repo root), already known to exist.
|
|
297
|
+
*parts: Repo-relative segments to append (member path, artifact rel path).
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
The resolved, contained absolute path.
|
|
301
|
+
|
|
302
|
+
Raises:
|
|
303
|
+
UsageError: If the resolved path escapes ``base`` (exit 2).
|
|
304
|
+
"""
|
|
305
|
+
base_real = base.resolve()
|
|
306
|
+
resolved = (base_real / Path(*parts)).resolve()
|
|
307
|
+
if resolved != base_real and base_real not in resolved.parents:
|
|
308
|
+
raise UsageError(f"path escapes target dir: {os.path.join(*parts)}")
|
|
309
|
+
return resolved
|
|
310
|
+
|
|
311
|
+
|
|
284
312
|
def _atomic_write_text(path: Path, text: str) -> None:
|
|
285
313
|
"""Write ``text`` to ``path`` atomically via a same-dir temp file + os.replace.
|
|
286
314
|
|
|
@@ -381,7 +409,7 @@ def run(
|
|
|
381
409
|
|
|
382
410
|
|
|
383
411
|
# --------------------------------------------------------------------------- #
|
|
384
|
-
#
|
|
412
|
+
# Subcommands: check / scaffold / verify / commit / status (02 §8.2)
|
|
385
413
|
# --------------------------------------------------------------------------- #
|
|
386
414
|
|
|
387
415
|
|
|
@@ -476,6 +504,7 @@ def _write_artifact(
|
|
|
476
504
|
"""
|
|
477
505
|
if rel_path in sentinel["artifactsWritten"]:
|
|
478
506
|
return
|
|
507
|
+
contained_path(target, rel_path) # never write outside the target repo (exit 2)
|
|
479
508
|
dest = target / rel_path
|
|
480
509
|
if dest.exists():
|
|
481
510
|
return
|
|
@@ -529,7 +558,7 @@ def _resolve_commands(member: Member) -> tuple[str, str]:
|
|
|
529
558
|
|
|
530
559
|
|
|
531
560
|
def write_config(answers: Answers, target: Path, sentinel: Sentinel) -> None:
|
|
532
|
-
"""Write forge.config.json
|
|
561
|
+
"""Write forge.config.json == forge-init's field set + loopRunner (02 §4.3, 00 §7)."""
|
|
533
562
|
config: dict = {
|
|
534
563
|
"specsDir": "./specs",
|
|
535
564
|
"docsDir": "./docs/architecture",
|
|
@@ -540,6 +569,14 @@ def write_config(answers: Answers, target: Path, sentinel: Sentinel) -> None:
|
|
|
540
569
|
"typeCheckCommand": None,
|
|
541
570
|
"testCommand": None,
|
|
542
571
|
"loopIterationMultiplier": 1.5,
|
|
572
|
+
# Navigator keys — kept in lockstep with forge-init.sh's emitted field set
|
|
573
|
+
# (00 §7, REQ-CFG-02) so a bootstrapped config == forge-init's + loopRunner.
|
|
574
|
+
"autoInvokeNextStage": True,
|
|
575
|
+
"contextWindowTokens": None,
|
|
576
|
+
"contextWarnThreshold": 0.7,
|
|
577
|
+
"autoVerify": False,
|
|
578
|
+
"autoVerifyStages": {},
|
|
579
|
+
"autoFix": False,
|
|
543
580
|
"loopRunner": {"name": "rauf", "bin": "rauf"},
|
|
544
581
|
}
|
|
545
582
|
if answers["layout"] == "single":
|
|
@@ -697,9 +734,9 @@ def scaffold(target: Path, answers: Answers) -> list[str]:
|
|
|
697
734
|
def toolchain_present(required: list[str]) -> bool:
|
|
698
735
|
"""Return True iff every required tool is on PATH (REQ-LIFE-03).
|
|
699
736
|
|
|
700
|
-
Probes each binary with
|
|
701
|
-
|
|
702
|
-
|
|
737
|
+
Probes each binary with :func:`shutil.which` (a pure PATH lookup, no shell
|
|
738
|
+
spawned). A single missing tool yields False, driving the distinct
|
|
739
|
+
missing-toolchain outcome (exit 2, 00 §9): the skill then offers
|
|
703
740
|
scaffold-anyway-unverified vs abort and marks the baseline unverified
|
|
704
741
|
(REQ-LIFE-04). Bootstrap NEVER installs a toolchain (tech-spec §9).
|
|
705
742
|
|
|
@@ -708,17 +745,10 @@ def toolchain_present(required: list[str]) -> bool:
|
|
|
708
745
|
already {pm}-substituted.
|
|
709
746
|
|
|
710
747
|
Returns:
|
|
711
|
-
True iff
|
|
748
|
+
True iff every entry resolves on PATH.
|
|
712
749
|
"""
|
|
713
750
|
for tool in required:
|
|
714
|
-
|
|
715
|
-
proc = run(["sh", "-c", f"command -v {tool}"], cwd=Path.cwd(), check=False)
|
|
716
|
-
except UsageError:
|
|
717
|
-
# The probe itself could not be launched (e.g. an empty PATH leaves no
|
|
718
|
-
# `sh`): treat that as the tool being absent, the missing-toolchain
|
|
719
|
-
# outcome, never an internal error (REQ-LIFE-03/04).
|
|
720
|
-
return False
|
|
721
|
-
if proc.returncode != 0:
|
|
751
|
+
if shutil.which(tool) is None:
|
|
722
752
|
return False
|
|
723
753
|
return True
|
|
724
754
|
|
|
@@ -763,9 +793,11 @@ def verify(target: Path, answers: Answers) -> VerifyResult:
|
|
|
763
793
|
test: list[CommandOutcome] = []
|
|
764
794
|
for member in answers["members"]:
|
|
765
795
|
lint_cmd, test_cmd = _resolve_commands(member)
|
|
766
|
-
cwd = target
|
|
796
|
+
cwd = contained_path(target, member["path"]) # never run outside the target
|
|
767
797
|
for bucket, cmd in ((lint, lint_cmd), (test, test_cmd)):
|
|
768
|
-
|
|
798
|
+
# STACK_COMMANDS templates with an allow-listed {pm} (see _validate_answers)
|
|
799
|
+
# — split into an argv token list and run without a shell (no `sh -c`).
|
|
800
|
+
proc = run(shlex.split(cmd), cwd=cwd, check=False)
|
|
769
801
|
bucket.append(
|
|
770
802
|
{"command": cmd, "ok": proc.returncode == 0, "member": member["path"]}
|
|
771
803
|
)
|
|
@@ -867,9 +899,55 @@ def _parse_answers(raw: str) -> Answers:
|
|
|
867
899
|
raise UsageError(f"malformed --answers JSON: {exc}")
|
|
868
900
|
if not isinstance(parsed, dict):
|
|
869
901
|
raise UsageError("--answers must be a JSON object")
|
|
902
|
+
_validate_answers(parsed)
|
|
870
903
|
return parsed
|
|
871
904
|
|
|
872
905
|
|
|
906
|
+
def _validate_answers(answers: Answers) -> None:
|
|
907
|
+
"""Reject non-allowlisted stack/packageManager and unsafe member paths (exit 2).
|
|
908
|
+
|
|
909
|
+
Runs before any ``{pm}`` substitution, ``STACK_COMMANDS[...]`` subscript, or
|
|
910
|
+
artifact write so a malformed ``--answers`` payload fails with a stderr
|
|
911
|
+
diagnostic, never a KeyError traceback or a path escape. Mirrors the
|
|
912
|
+
input-validation rigor epic-manifest.py applies via ``assert_safe_name`` /
|
|
913
|
+
``contained_path``:
|
|
914
|
+
|
|
915
|
+
- ``stack`` must be a known :data:`Stack` (a ``STACK_COMMANDS`` key).
|
|
916
|
+
- ``packageManager`` must be in ``PACKAGE_MANAGERS[stack]`` for a stack that
|
|
917
|
+
has a choice; stacks with no choice (go/rust/generic) must leave it ``None``.
|
|
918
|
+
- ``path`` must be a repo-relative string — no absolute path, no ``..``
|
|
919
|
+
component, no backslash — so it can never resolve outside the target.
|
|
920
|
+
"""
|
|
921
|
+
members = answers.get("members")
|
|
922
|
+
if not isinstance(members, list) or not members:
|
|
923
|
+
raise UsageError("--answers must include a non-empty members[] array")
|
|
924
|
+
for member in members:
|
|
925
|
+
stack = member.get("stack")
|
|
926
|
+
if stack not in STACK_COMMANDS:
|
|
927
|
+
raise UsageError(
|
|
928
|
+
f"unknown stack {stack!r}: expected one of {sorted(STACK_COMMANDS)}"
|
|
929
|
+
)
|
|
930
|
+
pm = member.get("packageManager")
|
|
931
|
+
choices = PACKAGE_MANAGERS.get(stack)
|
|
932
|
+
if choices is not None:
|
|
933
|
+
if pm not in choices:
|
|
934
|
+
raise UsageError(
|
|
935
|
+
f"invalid packageManager {pm!r} for stack {stack!r}: "
|
|
936
|
+
f"expected one of {choices}"
|
|
937
|
+
)
|
|
938
|
+
elif pm is not None:
|
|
939
|
+
raise UsageError(
|
|
940
|
+
f"stack {stack!r} takes no packageManager, got {pm!r}"
|
|
941
|
+
)
|
|
942
|
+
path = member.get("path")
|
|
943
|
+
if not isinstance(path, str) or not path:
|
|
944
|
+
raise UsageError(f"member path must be a non-empty string, got {path!r}")
|
|
945
|
+
if os.path.isabs(path) or "\\" in path or ".." in Path(path).parts:
|
|
946
|
+
raise UsageError(
|
|
947
|
+
f"unsafe member path {path!r}: must be repo-relative with no '..'"
|
|
948
|
+
)
|
|
949
|
+
|
|
950
|
+
|
|
873
951
|
def _dispatch(args: argparse.Namespace, target: Path) -> int:
|
|
874
952
|
"""Route a parsed command to its handler, translating outcomes into exit codes.
|
|
875
953
|
|
|
@@ -24,7 +24,10 @@ cat > "$CONFIG_FILE" << 'EOF'
|
|
|
24
24
|
"loopIterationMultiplier": 1.5,
|
|
25
25
|
"autoInvokeNextStage": true,
|
|
26
26
|
"contextWindowTokens": null,
|
|
27
|
-
"contextWarnThreshold": 0.7
|
|
27
|
+
"contextWarnThreshold": 0.7,
|
|
28
|
+
"autoVerify": false,
|
|
29
|
+
"autoVerifyStages": {},
|
|
30
|
+
"autoFix": false
|
|
28
31
|
}
|
|
29
32
|
EOF
|
|
30
33
|
|
|
@@ -43,6 +46,9 @@ echo " loopIterationMultiplier: 1.5 (multiplier for loop iterations)"
|
|
|
43
46
|
echo " autoInvokeNextStage: true (navigator auto-starts the next stage after you confirm)"
|
|
44
47
|
echo " contextWindowTokens: null (infer; set to 1000000 on a 1M-context model)"
|
|
45
48
|
echo " contextWarnThreshold: 0.7 (suggest a clean session past this fraction of the window)"
|
|
49
|
+
echo " autoVerify: false (set true to run forge-verify automatically after each stage)"
|
|
50
|
+
echo " autoVerifyStages: {} (per-stage overrides for autoVerify)"
|
|
51
|
+
echo " autoFix: false (set true to chain forge-fix after an auto-verify finds issues)"
|
|
46
52
|
echo ""
|
|
47
53
|
echo "The loop runner defaults to rauf. To target a different ralph-style runner,"
|
|
48
54
|
echo "add a \"loopRunner\" block (see references/forge-config-schema.json)."
|
|
@@ -31,14 +31,33 @@ if is_root "$root"; then
|
|
|
31
31
|
exit 0
|
|
32
32
|
fi
|
|
33
33
|
|
|
34
|
+
# ── Step 2a: Claude marketplace-cache installs — ~/.claude/plugins/cache/<mp>/<plugin>/<ver>/.
|
|
35
|
+
# This is where Claude Code actually installs marketplace plugins (three segments below
|
|
36
|
+
# plugins/, so the single-star plugins/*/feature-forge glob below can never match it). Version
|
|
37
|
+
# dirs can coexist after upgrades, so probe newest plugin.json first — a stale version must
|
|
38
|
+
# never shadow the current install. Deliberately ordered BEFORE the plugins/* glob: that glob
|
|
39
|
+
# can match the marketplace *clone* (~/.claude/plugins/marketplaces/<mp>/ when the marketplace
|
|
40
|
+
# repo root is itself a plugin root), which may sit at a different commit than the installed
|
|
41
|
+
# skills — the versioned cache install must always win to prevent that version skew.
|
|
42
|
+
while IFS= read -r manifest; do
|
|
43
|
+
candidate="${manifest%/.claude-plugin/plugin.json}"
|
|
44
|
+
if is_root "$candidate"; then
|
|
45
|
+
printf '%s\n' "$candidate"
|
|
46
|
+
exit 0
|
|
47
|
+
fi
|
|
48
|
+
done < <(ls -t "$HOME"/.claude/plugins/cache/*/feature-forge/*/.claude-plugin/plugin.json 2>/dev/null || true)
|
|
49
|
+
|
|
34
50
|
# ── Step 2: candidate-root probe (authoritative multi-agent root list; extend here first). ─
|
|
35
51
|
# Globs that match nothing expand to themselves; the is_root test rejects such literals. Covers
|
|
36
52
|
# every supported agent's install destination under BOTH global ($HOME) and project ($PWD) scope,
|
|
37
53
|
# matching the installer's per-agent layout: claude .claude/skills, codex .agents/skills, copilot
|
|
38
|
-
# .github/feature-forge, cursor .cursor/rules, gemini .gemini/extensions.
|
|
54
|
+
# .github/feature-forge, cursor .cursor/rules, gemini .gemini/extensions. The cache glob repeats
|
|
55
|
+
# step 2a's path for a cache install that carries only the neutral bundle sentinel (no
|
|
56
|
+
# plugin.json for ls -t to key on).
|
|
39
57
|
for candidate in \
|
|
40
58
|
"$HOME/.claude/skills/feature-forge" \
|
|
41
59
|
"$PWD/.claude/skills/feature-forge" \
|
|
60
|
+
"$HOME"/.claude/plugins/cache/*/feature-forge/* \
|
|
42
61
|
"$HOME"/.claude/plugins/*/feature-forge \
|
|
43
62
|
"$HOME/.agents/skills/feature-forge" \
|
|
44
63
|
"$PWD/.agents/skills/feature-forge" \
|