@jamie-tam/forge 6.0.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/LICENSE +21 -0
- package/README.md +389 -0
- package/agents/architect.md +92 -0
- package/agents/builder.md +122 -0
- package/agents/code-reviewer.md +107 -0
- package/agents/concept-designer.md +207 -0
- package/agents/craft-reviewer.md +132 -0
- package/agents/critic.md +130 -0
- package/agents/doc-writer.md +85 -0
- package/agents/dreamer.md +129 -0
- package/agents/e2e-runner.md +89 -0
- package/agents/gotcha-hunter.md +127 -0
- package/agents/prototype-builder.md +193 -0
- package/agents/prototype-codifier.md +204 -0
- package/agents/prototype-reviewer.md +163 -0
- package/agents/security-reviewer.md +108 -0
- package/agents/spec-reviewer.md +94 -0
- package/agents/tracer.md +98 -0
- package/agents/wireframer.md +109 -0
- package/commands/abort.md +25 -0
- package/commands/bugfix.md +151 -0
- package/commands/evolve.md +118 -0
- package/commands/feature.md +236 -0
- package/commands/forge.md +100 -0
- package/commands/greenfield.md +185 -0
- package/commands/hotfix.md +98 -0
- package/commands/refactor.md +147 -0
- package/commands/resume.md +25 -0
- package/commands/setup.md +201 -0
- package/commands/status.md +27 -0
- package/commands/task-force.md +110 -0
- package/commands/validate.md +12 -0
- package/dist/__tests__/active-manifest.test.js +272 -0
- package/dist/__tests__/copy.test.js +96 -0
- package/dist/__tests__/gate-check.test.js +384 -0
- package/dist/__tests__/wiki.test.js +472 -0
- package/dist/__tests__/work-manifest.test.js +304 -0
- package/dist/active-manifest.js +229 -0
- package/dist/cli.js +158 -0
- package/dist/copy.js +124 -0
- package/dist/gate-check.js +326 -0
- package/dist/hooks.js +60 -0
- package/dist/init.js +140 -0
- package/dist/manifest.js +90 -0
- package/dist/merge.js +77 -0
- package/dist/paths.js +36 -0
- package/dist/uninstall.js +216 -0
- package/dist/update.js +158 -0
- package/dist/verify-manifest.js +65 -0
- package/dist/verify.js +98 -0
- package/dist/wiki-ui.js +310 -0
- package/dist/wiki.js +364 -0
- package/dist/work-manifest.js +798 -0
- package/hooks/config/gate-requirements.json +79 -0
- package/hooks/hooks.json +143 -0
- package/hooks/scripts/analyze-telemetry.sh +114 -0
- package/hooks/scripts/gate-enforcer.sh +164 -0
- package/hooks/scripts/pre-compact.sh +90 -0
- package/hooks/scripts/session-start.sh +81 -0
- package/hooks/scripts/telemetry.sh +41 -0
- package/hooks/scripts/wiki-lint.sh +87 -0
- package/hooks/templates/AGENTS.md.template +48 -0
- package/hooks/templates/CLAUDE.md.template +45 -0
- package/package.json +55 -0
- package/protocols/README.md +40 -0
- package/protocols/codex.md +151 -0
- package/protocols/graphify.md +156 -0
- package/references/common/agent-coordination.md +65 -0
- package/references/common/coding-standards.md +54 -0
- package/references/common/feature-tracking.md +21 -0
- package/references/common/io-protocol.md +36 -0
- package/references/common/phases.md +57 -0
- package/references/common/quality-gates.md +130 -0
- package/references/common/skill-authoring.md +154 -0
- package/references/common/skill-compliance.md +30 -0
- package/references/python/standards.md +44 -0
- package/references/react/standards.md +61 -0
- package/references/typescript/standards.md +42 -0
- package/rules/common/forge-system.md +59 -0
- package/rules/common/git-workflow.md +40 -0
- package/rules/common/guardrails.md +37 -0
- package/rules/common/quality-gates.md +18 -0
- package/rules/common/security.md +50 -0
- package/rules/common/skill-selection.md +78 -0
- package/rules/common/testing.md +58 -0
- package/rules/common/verification.md +39 -0
- package/skills/build-pr-workflow/SKILL.md +301 -0
- package/skills/build-pr-workflow/references/pr-template.md +62 -0
- package/skills/build-pr-workflow/references/subagent-merge.md +47 -0
- package/skills/build-pr-workflow/references/worktree-setup.md +125 -0
- package/skills/build-prototype/SKILL.md +264 -0
- package/skills/build-scaffold/SKILL.md +340 -0
- package/skills/build-tdd/SKILL.md +89 -0
- package/skills/build-wireframe/SKILL.md +110 -0
- package/skills/build-wireframe/assets/baseline-template.html +486 -0
- package/skills/build-wireframe/references/demo-walkthroughs.md +170 -0
- package/skills/build-wireframe/references/gotchas.md +188 -0
- package/skills/build-wireframe/references/legend-lines.md +141 -0
- package/skills/concept-slides/SKILL.md +192 -0
- package/skills/deliver-db-migration/SKILL.md +466 -0
- package/skills/deliver-deploy/SKILL.md +407 -0
- package/skills/deliver-onboarding/SKILL.md +198 -0
- package/skills/deliver-onboarding/references/document-templates.md +393 -0
- package/skills/deliver-onboarding/templates/getting-started.md +122 -0
- package/skills/discover-codebase-analysis/SKILL.md +448 -0
- package/skills/discover-requirements/SKILL.md +418 -0
- package/skills/discover-requirements/templates/prd.md +99 -0
- package/skills/discover-requirements/templates/technical-spec.md +123 -0
- package/skills/discover-requirements/templates/user-stories.md +76 -0
- package/skills/harden/SKILL.md +214 -0
- package/skills/iterate-prototype/SKILL.md +241 -0
- package/skills/plan-architecture/SKILL.md +457 -0
- package/skills/plan-architecture/templates/adr-template.md +52 -0
- package/skills/plan-architecture/templates/api-contract.md +99 -0
- package/skills/plan-architecture/templates/db-schema.md +81 -0
- package/skills/plan-architecture/templates/system-design.md +111 -0
- package/skills/plan-brainstorm/SKILL.md +433 -0
- package/skills/plan-design-system/SKILL.md +279 -0
- package/skills/plan-task-decompose/SKILL.md +454 -0
- package/skills/quality-code-review/SKILL.md +286 -0
- package/skills/quality-security-audit/SKILL.md +292 -0
- package/skills/quality-security-audit/references/audit-report-template.md +89 -0
- package/skills/quality-security-audit/references/owasp-checks.md +178 -0
- package/skills/quality-test-execution/SKILL.md +435 -0
- package/skills/quality-test-plan/SKILL.md +297 -0
- package/skills/quality-test-plan/references/test-type-guide.md +263 -0
- package/skills/quality-test-plan/templates/e2e-test-plan.md +72 -0
- package/skills/quality-test-plan/templates/integration-test-plan.md +74 -0
- package/skills/quality-test-plan/templates/load-test-plan.md +111 -0
- package/skills/quality-test-plan/templates/smoke-test-plan.md +68 -0
- package/skills/quality-test-plan/templates/unit-test-plan.md +56 -0
- package/skills/quality-uiux/SKILL.md +481 -0
- package/skills/support-debug/SKILL.md +464 -0
- package/skills/support-dream/SKILL.md +213 -0
- package/skills/support-gotcha/SKILL.md +249 -0
- package/skills/support-runtime-reachability/SKILL.md +190 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-01-passes-app-use/src/app.ts +7 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-01-passes-app-use/src/handlers/cases.ts +7 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-02-orphan-no-app-use/src/app.ts +8 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-02-orphan-no-app-use/src/handlers/cases.ts +7 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-03-orphan-import-only/src/App.tsx +5 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-03-orphan-import-only/src/components/RingingBanner.tsx +7 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-03-orphan-import-only/src/hooks/useTwilio.ts +6 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-04-jsx-component-rendered/src/App.tsx +5 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-04-jsx-component-rendered/src/components/MyComp.tsx +3 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-05-jsx-component-not-rendered/src/App.tsx +3 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-05-jsx-component-not-rendered/src/components/Orphan.tsx +3 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-06-class-instantiated/src/lib/Service.ts +6 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-06-class-instantiated/src/main.ts +4 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-07-class-not-instantiated/src/lib/Lonely.ts +5 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-07-class-not-instantiated/src/main.ts +2 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-08-default-export-imported-and-called/src/handler.ts +3 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-08-default-export-imported-and-called/src/main.ts +3 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-09-default-export-orphan/src/handler.ts +3 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-09-default-export-orphan/src/main.ts +2 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-10-aliased-named-export/src/lib.ts +5 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-10-aliased-named-export/src/main.ts +3 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-11-re-export-chain/src/lib/index.ts +1 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-11-re-export-chain/src/lib/internal.ts +3 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-11-re-export-chain/src/main.ts +3 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-12-test-only-caller/src/util.test.ts +5 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-12-test-only-caller/src/util.ts +3 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-13-gated-pending-annotation/src/future.ts +4 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-14-untraceable-annotation/src/decorated.ts +4 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-15-untraceable-empty/src/lazy.ts +4 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-16-python-module/src/lib.py +15 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-16-python-module/src/main.py +5 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-17-router-use/src/parent.ts +5 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-17-router-use/src/routes/cases.ts +5 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-18-shadowed-name-fp/src/lib/foo.ts +3 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-18-shadowed-name-fp/src/other.ts +8 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-19-same-name-different-module/src/handlers/cases.ts +4 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-19-same-name-different-module/src/handlers/users.ts +4 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-19-same-name-different-module/src/main.ts +5 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-20-aliased-import-usage/src/handlers/cases.ts +3 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-20-aliased-import-usage/src/main.ts +4 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-21-mixed-default-and-named/src/lib.ts +5 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-21-mixed-default-and-named/src/main.ts +5 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-22-dynamic-import-then-caller/src/lib.ts +3 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-22-dynamic-import-then-caller/src/main.ts +8 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-23-dynamic-import-with-space/src/lib.ts +3 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-23-dynamic-import-with-space/src/main.ts +7 -0
- package/skills/support-runtime-reachability/scripts/check.mjs +638 -0
- package/skills/support-runtime-reachability/scripts/check.test.mjs +244 -0
- package/skills/support-skill-validator/SKILL.md +194 -0
- package/skills/support-skill-validator/references/false-positives.md +59 -0
- package/skills/support-skill-validator/references/validation-checks.md +280 -0
- package/skills/support-system-guide/SKILL.md +311 -0
- package/skills/support-task-force/SKILL.md +265 -0
- package/skills/support-task-force/references/dispatch-pattern.md +178 -0
- package/skills/support-task-force/references/synthesis-template.md +126 -0
- package/skills/support-wiki-bootstrap/SKILL.md +37 -0
- package/skills/support-wiki-lint/SKILL.md +196 -0
- package/skills/support-wiki-lint/scripts/lint.mjs +488 -0
- package/skills/support-wiki-lint/scripts/lint.test.mjs +196 -0
- package/templates/README.md +23 -0
- package/templates/aiwiki/CLAUDE.md.template +78 -0
- package/templates/aiwiki/schemas/architecture.md +118 -0
- package/templates/aiwiki/schemas/convention.md +112 -0
- package/templates/aiwiki/schemas/decision.md +144 -0
- package/templates/aiwiki/schemas/gotcha.md +118 -0
- package/templates/aiwiki/schemas/oracle.md +105 -0
- package/templates/aiwiki/schemas/session.md +125 -0
- package/templates/manifests/bugfix.yaml +41 -0
- package/templates/manifests/feature.yaml +69 -0
- package/templates/manifests/greenfield.yaml +61 -0
- package/templates/manifests/hotfix.yaml +45 -0
- package/templates/manifests/refactor.yaml +44 -0
- package/templates/manifests/v5/SCHEMA.md +327 -0
- package/templates/manifests/v5/feature.yaml +77 -0
- package/templates/manifests/v6/SCHEMA.md +199 -0
- package/templates/wiki-html/dream-detail.html +378 -0
- package/templates/wiki-html/dreams-list.html +155 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: setup
|
|
3
|
+
description: "Infer project stack from manifests, fill in the CLAUDE.md and AGENTS.md project profiles, and scaffold the .forge and aiwiki directories. Run after forge init."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /setup — Configure Project Profile
|
|
7
|
+
|
|
8
|
+
You are configuring the forge project profile. The CLI (`npx @jamie-tam/forge init`) already installed skills, commands, agents, hooks, all language rules, and the CLAUDE.md / AGENTS.md templates. This command infers the project's tech profile, fills the templates, and scaffolds the working directories.
|
|
9
|
+
|
|
10
|
+
Language rules ship complete (typescript, react, python). Agents read the rule files matching the work at hand — no install-time pruning needed.
|
|
11
|
+
|
|
12
|
+
## Step 1: Detect Optional Tools & Build Graph
|
|
13
|
+
|
|
14
|
+
Detect optional tools first — Graphify's knowledge graph enriches stack inference and reduces tokens for all later steps.
|
|
15
|
+
|
|
16
|
+
**Graphify (knowledge graph):**
|
|
17
|
+
|
|
18
|
+
Run the graphify status check from `protocols/graphify.md`:
|
|
19
|
+
|
|
20
|
+
1. Check three things:
|
|
21
|
+
- `graphify-out/graph.json` exists?
|
|
22
|
+
- `graphify --version` succeeds (CLI installed)?
|
|
23
|
+
- `~/.claude/skills/graphify/SKILL.md` exists (skill installed for `/graphify` to work)?
|
|
24
|
+
2. Prompt based on status (see the full guard table in `protocols/graphify.md`):
|
|
25
|
+
- **Nothing installed** → offer install (CLI + skill manual path from protocol)
|
|
26
|
+
- **CLI installed, skill missing** → offer skill install only (so `/graphify` works)
|
|
27
|
+
- **Both installed, no graph** → offer build (`/graphify`)
|
|
28
|
+
- **Graph exists, both installed** → offer update (`/graphify --update`)
|
|
29
|
+
- **Graph exists, CLI missing** → use static files only
|
|
30
|
+
3. If user accepts build/update → invoke the `/graphify` skill, then continue.
|
|
31
|
+
4. If graph is available after this step, it will enrich Step 2.
|
|
32
|
+
|
|
33
|
+
**Codex plugin:**
|
|
34
|
+
```bash
|
|
35
|
+
codex --version 2>/dev/null
|
|
36
|
+
```
|
|
37
|
+
- If detected: note the version for the report.
|
|
38
|
+
- If not detected: do not mention it.
|
|
39
|
+
|
|
40
|
+
**Gitignore setup:**
|
|
41
|
+
Ensure `.forge/local.yaml` is in `.gitignore`. This file stores per-user preferences (e.g., Codex consent, Graphify consent) and must not be committed.
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
grep -q '.forge/local.yaml' .gitignore 2>/dev/null || echo '.forge/local.yaml' >> .gitignore
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
<GATE>
|
|
48
|
+
STOP. If Graphify was recommended for installation or a graph build was offered, wait for the user's response before proceeding. Do NOT skip ahead to stack inference — the graph data from this step enriches the next step. Only proceed to Step 2 after the user has responded to all tool recommendations.
|
|
49
|
+
</GATE>
|
|
50
|
+
|
|
51
|
+
## Step 2: Infer Project Profile & Fill Templates
|
|
52
|
+
|
|
53
|
+
Read the project's manifest files to infer the tech profile. Check whichever exist:
|
|
54
|
+
|
|
55
|
+
| Look for | Tells you |
|
|
56
|
+
|---|---|
|
|
57
|
+
| `package.json` | name, dependencies, frameworks (React/Vue/Next/Astro/Hono/etc.), test runner (vitest/jest/playwright), bundler |
|
|
58
|
+
| `pnpm-workspace.yaml`, `package.json` `workspaces` | monorepo structure — scan each workspace's manifest |
|
|
59
|
+
| `pyproject.toml`, `requirements.txt`, `Pipfile` | Python deps, framework (FastAPI/Django/Flask), test runner (pytest) |
|
|
60
|
+
| `go.mod`, `Cargo.toml`, `pom.xml`, `build.gradle` | Go / Rust / Java / Kotlin |
|
|
61
|
+
| Lock files (`pnpm-lock.yaml`, `yarn.lock`, `bun.lock`, `package-lock.json`, `uv.lock`, `poetry.lock`) | Package manager |
|
|
62
|
+
| `tsconfig.json` | TypeScript |
|
|
63
|
+
| `Dockerfile`, `docker-compose.yml` | Container setup, often database hints |
|
|
64
|
+
| `vite.config.*`, `next.config.*`, `astro.config.*`, etc. | Confirm framework |
|
|
65
|
+
| `tailwind.config.*`, `postcss.config.*` | Styling stack |
|
|
66
|
+
|
|
67
|
+
If a Graphify graph is available, cross-reference its technology nodes and community labels to validate and enrich the inference (especially for monorepos and modern frameworks the manifests don't fully reveal).
|
|
68
|
+
|
|
69
|
+
Synthesize what you find into a profile. Aim for accurate version numbers (read package versions, not just names). Present for confirmation:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
Inferred project profile:
|
|
73
|
+
Name: my-app
|
|
74
|
+
Stack: typescript/react 18 + next 14
|
|
75
|
+
Database: postgresql (via @prisma/client)
|
|
76
|
+
Unit tests: vitest
|
|
77
|
+
E2E tests: playwright
|
|
78
|
+
Package manager: pnpm
|
|
79
|
+
Notable libs: zustand, tailwind 4, lucide-react
|
|
80
|
+
|
|
81
|
+
Is this right? (y / correct field=value / describe what's off)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Let the user correct any fields. Honor user corrections — they know their project; you're inferring.
|
|
85
|
+
|
|
86
|
+
<GATE>
|
|
87
|
+
STOP. Do NOT proceed until the user has confirmed or corrected the inferred profile. The values flow into project docs and the wiki — wrong values mislead every later session.
|
|
88
|
+
</GATE>
|
|
89
|
+
|
|
90
|
+
After confirmation, fill both project docs. Show the filled content for review before writing:
|
|
91
|
+
|
|
92
|
+
- **`.claude/CLAUDE.md`** — populate the `project:` YAML block: name, stack, database, test_runner, package_manager.
|
|
93
|
+
- **`AGENTS.md`** (repo root) — populate the Project section: Name, Stack, Test runner, Package manager. Replace the `(run /setup)` placeholders with actual values.
|
|
94
|
+
|
|
95
|
+
<GATE>
|
|
96
|
+
STOP. Present the filled CLAUDE.md and AGENTS.md content to the user for review before writing. Do NOT write files without user confirmation.
|
|
97
|
+
</GATE>
|
|
98
|
+
|
|
99
|
+
## Step 2a: Configure Gate Enforcement Mode
|
|
100
|
+
|
|
101
|
+
`npx @jamie-tam/forge init` installs PreToolUse hooks that block manifest `gate-passed: true` edits unless telemetry shows the matching skill was invoked via the Skill tool. Useful for production-grade work; high-friction for prototype iteration where most phases are explicitly skipped.
|
|
102
|
+
|
|
103
|
+
**Default suggestion** (computed from Step 2 profile):
|
|
104
|
+
- If signals match **prototype mode** (path under `pocs/`, `package.json` `"private": true` with no CI, no `aiwiki/architecture/`): suggest **disable**
|
|
105
|
+
- If signals match **production mode** (CI configured, tests present, codified architecture exists or imminent): suggest **keep enabled**
|
|
106
|
+
|
|
107
|
+
Ask the user:
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
Gate enforcement (PreToolUse hooks that block manifest gate-passed edits without skill-invocation telemetry):
|
|
111
|
+
|
|
112
|
+
Suggested for this project: <KEEP ENABLED | DISABLE>
|
|
113
|
+
|
|
114
|
+
• Keep enabled — production-grade discipline; manifests cannot lie about gate state
|
|
115
|
+
• Disable — prototype iteration; faster loop, telemetry still recorded for retrospective audit
|
|
116
|
+
|
|
117
|
+
Choice? [Y]es (keep enabled) / [d]isable / [k]eep enabled regardless of suggestion
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**On "disable"**: remove the two gate-enforcer matcher entries from `.claude/settings.local.json` (the entries with `matcher: Edit` or `matcher: Write` whose command invokes `.claude/hooks/scripts/gate-enforcer.sh`). The file is gitignored so this is a local-only change. Keep the PostToolUse telemetry hook — telemetry is still recorded for retrospective audit; only the blocking is removed.
|
|
121
|
+
|
|
122
|
+
**On "keep enabled"**: leave settings.local.json as installed.
|
|
123
|
+
|
|
124
|
+
Tell the user: "Gate enforcement <enabled / disabled>. To toggle later, edit `.claude/settings.local.json` — see `hooks/hooks.json` in the forge repo for the canonical entries."
|
|
125
|
+
|
|
126
|
+
Note in the manifest or session memo what was chosen, so future sessions don't have to re-detect.
|
|
127
|
+
|
|
128
|
+
## Step 3: Create .forge Directory
|
|
129
|
+
|
|
130
|
+
Create the working directory structure if it doesn't exist:
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
.forge/
|
|
134
|
+
├── work/ # Per-work-item artifacts and manifests, one subdir per type
|
|
135
|
+
│ ├── feature/
|
|
136
|
+
│ ├── bugfix/
|
|
137
|
+
│ ├── refactor/
|
|
138
|
+
│ ├── hotfix/
|
|
139
|
+
│ └── greenfield/
|
|
140
|
+
├── state/ # Runtime state (telemetry, wiki receipts, dream history)
|
|
141
|
+
└── wiki-history/ # Pre-swap snapshots of aiwiki/ before atomic accept (for rollback)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Only create the top-level directories. Individual work-item folders are created on demand by the relevant command.
|
|
145
|
+
|
|
146
|
+
## Step 4: Create aiwiki Directory
|
|
147
|
+
|
|
148
|
+
The `aiwiki/` is the project's persistent context layer — typed pages (decisions, gotchas, conventions, architecture, sessions), an auto-loaded usage rules file, and the schema definitions LINT validates against. Scaffold from templates installed by `npx @jamie-tam/forge init`:
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
aiwiki/
|
|
152
|
+
├── CLAUDE.md # Auto-loaded by Claude Code at session start (wiki usage rules)
|
|
153
|
+
├── INDEX.md # Sortable index of recent activity (dream-maintained)
|
|
154
|
+
├── projectbrief.md # One-pager: what this project is (filled by user)
|
|
155
|
+
├── decisions/ # ADRs
|
|
156
|
+
├── gotchas/ # Recurring failures + prevention
|
|
157
|
+
├── conventions/ # Codebase patterns + rationale
|
|
158
|
+
├── architecture/ # System-shape docs (one file per subsystem)
|
|
159
|
+
├── sessions/ # Per-session handoff index (hook + dream maintained)
|
|
160
|
+
├── raw/ # Incoming, unclassified — compile or delete at phase close
|
|
161
|
+
├── proposed/ # Dream output awaiting user review (input untouched)
|
|
162
|
+
└── schemas/ # Per-page-type schemas used by wiki-lint
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
REQUIRED SUB-SKILL: Use **support-wiki-bootstrap** to ensure aiwiki/ exists.
|
|
166
|
+
|
|
167
|
+
After bootstrap, update `aiwiki/projectbrief.md` with the project name and stack from Step 2 (replace the placeholder description). Ask the user to fill `projectbrief.md` before continuing — the wiki references it for orientation context.
|
|
168
|
+
|
|
169
|
+
<GATE>
|
|
170
|
+
STOP. The aiwiki/CLAUDE.md is auto-loaded by Claude Code on every session start in this project. Confirm with the user that they accept the wiki usage rules (citation requirements, typed page schemas, no-speculation rule, dream review flow) before completing setup. They can edit aiwiki/CLAUDE.md to customize.
|
|
171
|
+
</GATE>
|
|
172
|
+
|
|
173
|
+
## Step 5: Report
|
|
174
|
+
|
|
175
|
+
Print a summary:
|
|
176
|
+
```
|
|
177
|
+
Setup complete!
|
|
178
|
+
|
|
179
|
+
Project profile:
|
|
180
|
+
Name: {name}
|
|
181
|
+
Stack: {stack}
|
|
182
|
+
Test runner: {test_runner}
|
|
183
|
+
Package manager: {package_manager}
|
|
184
|
+
|
|
185
|
+
Configured:
|
|
186
|
+
- CLAUDE.md and AGENTS.md profiles filled
|
|
187
|
+
- .forge/ working directory (work/, state/, wiki-history/)
|
|
188
|
+
- aiwiki/ directory scaffolded (CLAUDE.md, schemas/, projectbrief.md, typed-page subdirs)
|
|
189
|
+
|
|
190
|
+
Optional tools:
|
|
191
|
+
- Codex plugin: {detected version | not detected}
|
|
192
|
+
{If detected: "Second opinions available during quality and planning phases."}
|
|
193
|
+
- Graphify: {detected version | "not installed — pip install graphifyy"}
|
|
194
|
+
{If detected + graph built: "Knowledge graph available ({N} nodes, {N} communities)."}
|
|
195
|
+
{If detected + no graph: "Run /graphify to build a knowledge graph."}
|
|
196
|
+
|
|
197
|
+
Next steps:
|
|
198
|
+
1. Review .claude/CLAUDE.md and AGENTS.md
|
|
199
|
+
2. Run /validate to verify system health
|
|
200
|
+
3. Start with /feature or /greenfield
|
|
201
|
+
```
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: status
|
|
3
|
+
description: "List in-flight .forge/work items and their current phase."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /status — Work Item Status
|
|
7
|
+
|
|
8
|
+
List active work items from `.forge/work/` without modifying files.
|
|
9
|
+
|
|
10
|
+
## Scope
|
|
11
|
+
|
|
12
|
+
Reports in-flight `.forge/work/` items and their current phase. Read-only — does not modify manifests, files, or wiki. Does NOT resume work, route between commands, or judge whether a stalled item should be aborted. Invoke when the user asks "what's in flight?", before `/resume` (to pick a target), or before `/abort` (to confirm which item).
|
|
13
|
+
|
|
14
|
+
## Steps
|
|
15
|
+
|
|
16
|
+
1. Find manifests at `.forge/work/*/*/manifest.yaml`.
|
|
17
|
+
2. Exclude items whose `status` is `completed`, `abandoned`, or `escalated`.
|
|
18
|
+
3. For each remaining item, infer the current phase from the first phase or gate that is not complete, locked, skipped, or not-applicable. Prefer explicit fields such as `build.current_phase` when present.
|
|
19
|
+
4. Print a table:
|
|
20
|
+
|
|
21
|
+
```markdown
|
|
22
|
+
| Type | Name | Status | Current phase | Manifest |
|
|
23
|
+
|---|---|---|---|---|
|
|
24
|
+
| feature | add-payments | in-progress | quality.test-execution | .forge/work/feature/add-payments/manifest.yaml |
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
If no in-flight items exist, say so clearly.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: task-force
|
|
3
|
+
description: "Dispatch a parallel task-force per item in a user-provided punch list. Phase-aware sizing (light teams in prototype phases, full crew in production). Routes command-shaped items (features, bugfixes, refactors) to their owning commands. Include !max (or --full-power) in your prompt to force full-crew production teams regardless of detected phase."
|
|
4
|
+
argument-hint: "task1; task2; task3 (or numbered/bulleted list); append !max to force full crew"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# /task-force
|
|
8
|
+
|
|
9
|
+
Run a parallel ad-hoc task-force dispatcher over a punch list of independent tasks.
|
|
10
|
+
|
|
11
|
+
## When to use
|
|
12
|
+
|
|
13
|
+
- You have 3-20 independent tasks to do in parallel
|
|
14
|
+
- The tasks are heterogeneous (mix of dev, research, docs, debug, quick fixes)
|
|
15
|
+
- None of the tasks individually warrants a full `/feature` or `/bugfix` workflow
|
|
16
|
+
- You want Codex-paired second opinions where it earns its keep
|
|
17
|
+
|
|
18
|
+
## When NOT to use
|
|
19
|
+
|
|
20
|
+
- A single task — use the appropriate skill or command directly
|
|
21
|
+
- A coherent feature spec — use `/feature`
|
|
22
|
+
- A single bug to fix — use `/bugfix`
|
|
23
|
+
- A refactor — use `/refactor`
|
|
24
|
+
- Everything in the list is command-shaped — invoke those commands instead
|
|
25
|
+
|
|
26
|
+
## Process
|
|
27
|
+
|
|
28
|
+
REQUIRED SUB-SKILL: Use **support-task-force** to:
|
|
29
|
+
1. Run Codex consent (one-time per run, per `protocols/codex.md`)
|
|
30
|
+
2. Detect repo phase (prototype vs production) from active manifest or repo state
|
|
31
|
+
3. Parse the task list (numbered, bulleted, or semicolon-separated argument)
|
|
32
|
+
4. Classify each task by type (dev / research / debug / docs / review / security / quick / complex / command-shaped)
|
|
33
|
+
5. Route command-shaped items to their owning commands (do NOT dispatch task-forces for them)
|
|
34
|
+
6. Assemble a team per task, sized by task type AND phase mode
|
|
35
|
+
7. Dispatch all task-forces in parallel (background subagents)
|
|
36
|
+
8. Aggregate per-task verdicts with Claude+Codex consensus check
|
|
37
|
+
9. Surface a run-summary with conflicts highlighted
|
|
38
|
+
|
|
39
|
+
## Arguments
|
|
40
|
+
|
|
41
|
+
Either:
|
|
42
|
+
- Pass the list directly: `/task-force update README; explore caching options; add test for parser`
|
|
43
|
+
- Or pass a numbered/bulleted list in the next prompt after invoking `/task-force` bare
|
|
44
|
+
|
|
45
|
+
## Output
|
|
46
|
+
|
|
47
|
+
`.forge/task-force/{run-id}/` containing:
|
|
48
|
+
- `codex.yaml` — Codex consent choice
|
|
49
|
+
- `tasks.yaml` — task classifications
|
|
50
|
+
- `task-{n}/{role}.md` — per-agent output
|
|
51
|
+
- `task-{n}/verdict.md` — per-task consensus verdict
|
|
52
|
+
- `run-summary.md` — consolidated report
|
|
53
|
+
|
|
54
|
+
## Caps
|
|
55
|
+
|
|
56
|
+
- Hard cap: 20 tasks per run
|
|
57
|
+
- Soft cap: 10 tasks (warn, offer to batch)
|
|
58
|
+
- Per-task team: 1-5 agents depending on phase mode and task type
|
|
59
|
+
- Total agents per run: ~5-80 typically
|
|
60
|
+
|
|
61
|
+
## Examples
|
|
62
|
+
|
|
63
|
+
### Standard (phase-aware sizing)
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
/task-force
|
|
67
|
+
1. Update README with new install steps
|
|
68
|
+
2. Explore options for adding a /status command
|
|
69
|
+
3. Add unit test for parser edge cases
|
|
70
|
+
4. Bump dep "yaml" to ^2.3
|
|
71
|
+
5. Investigate why CI is flaky on test 17
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Expected behavior (production phase detected):
|
|
75
|
+
- Task 1 (docs): `doc-writer` + Codex verify
|
|
76
|
+
- Task 2 (research): 2 explorers + Codex
|
|
77
|
+
- Task 3 (dev): `builder` + dispatch `quality-code-review`
|
|
78
|
+
- Task 4 (quick): one agent + Codex verify
|
|
79
|
+
- Task 5 (debug): dispatch `support-debug` + `gotcha-hunter` + Codex
|
|
80
|
+
- Total: ~12 agents in parallel
|
|
81
|
+
|
|
82
|
+
In prototype phase, the same list dispatches ~5 agents (light templates, no Codex on quick/docs).
|
|
83
|
+
|
|
84
|
+
### Force full power (`!max` override)
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
/task-force !max
|
|
88
|
+
1. Update README with new install steps
|
|
89
|
+
2. Add helper function for date parsing
|
|
90
|
+
3. Investigate why CI is flaky on test 17
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Expected behavior (production teams enforced even if repo is in prototype phase):
|
|
94
|
+
- All tasks dispatch with full production templates
|
|
95
|
+
- Mandatory Codex pairing throughout
|
|
96
|
+
- No "quick mode" shortcut
|
|
97
|
+
- Total: ~8 agents
|
|
98
|
+
|
|
99
|
+
Use `!max` (short form) or `--full-power` (canonical form) when prototype code is about to graduate to production, or when you want maximum review depth regardless of phase state. Both forms are equivalent.
|
|
100
|
+
|
|
101
|
+
## Manifest
|
|
102
|
+
|
|
103
|
+
This command does NOT create a `.forge/work/{type}/{name}/` manifest — task-force runs are not phase-gated. State lives in `.forge/task-force/{run-id}/` only.
|
|
104
|
+
|
|
105
|
+
If an active feature/bugfix manifest exists, the task-force run-id is appended to its current phase as `task-force-runs: [...]` for audit.
|
|
106
|
+
|
|
107
|
+
## See also
|
|
108
|
+
|
|
109
|
+
- `skills/support-task-force/SKILL.md` — full process documentation
|
|
110
|
+
- `/feature`, `/bugfix`, `/refactor` — for command-shaped work that this skill refuses to swallow
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: validate
|
|
3
|
+
description: "Validate that all skills, rules, and commands are consistent. Run after modifying skills or to check system health."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /validate — System Consistency Check
|
|
7
|
+
|
|
8
|
+
REQUIRED SUB-SKILL: Use **support-skill-validator** in **full scan mode**.
|
|
9
|
+
|
|
10
|
+
Run all five validation checks: directive conflicts, I/O graph integrity, responsibility overlaps, gate completeness, post-evolve drift.
|
|
11
|
+
|
|
12
|
+
Present results to the user. For Critical issues, suggest specific fixes. If fixes are applied, re-run to confirm resolution.
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smoke test for src/active-manifest.ts.
|
|
3
|
+
*
|
|
4
|
+
* Run via: npm run build && node dist/__tests__/active-manifest.test.js
|
|
5
|
+
*
|
|
6
|
+
* Uses a tmp dir as cwd so the real .forge/state is untouched.
|
|
7
|
+
*/
|
|
8
|
+
import { mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { clearActiveManifest, getActiveManifest, getActivePointerPath, setActiveManifest, } from "../active-manifest.js";
|
|
12
|
+
let passed = 0;
|
|
13
|
+
let failed = 0;
|
|
14
|
+
const fails = [];
|
|
15
|
+
function test(name, fn) {
|
|
16
|
+
try {
|
|
17
|
+
fn();
|
|
18
|
+
passed++;
|
|
19
|
+
console.log(` PASS ${name}`);
|
|
20
|
+
}
|
|
21
|
+
catch (e) {
|
|
22
|
+
failed++;
|
|
23
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
24
|
+
fails.push(`${name}: ${msg}`);
|
|
25
|
+
console.log(` FAIL ${name}\n ${msg}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function withTmpProject(fn) {
|
|
29
|
+
const tmp = mkdtempSync(join(tmpdir(), "forge-active-"));
|
|
30
|
+
// Make it a git repo so getStateDir resolves cleanly
|
|
31
|
+
try {
|
|
32
|
+
require("node:child_process").execSync("git init -q", { cwd: tmp });
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
/* ignore */
|
|
36
|
+
}
|
|
37
|
+
const manifestPath = join(tmp, "manifest.yaml");
|
|
38
|
+
writeFileSync(manifestPath, "schema_version: \"5\"\n");
|
|
39
|
+
try {
|
|
40
|
+
fn(tmp, manifestPath);
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
console.log("\n=== active-manifest.ts smoke tests ===\n");
|
|
47
|
+
test("get when not set returns null", () => {
|
|
48
|
+
withTmpProject((cwd) => {
|
|
49
|
+
const ptr = getActiveManifest(cwd);
|
|
50
|
+
if (ptr !== null)
|
|
51
|
+
throw new Error(`expected null, got ${JSON.stringify(ptr)}`);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
test("set + get roundtrip", () => {
|
|
55
|
+
withTmpProject((cwd, manifestPath) => {
|
|
56
|
+
const setResult = setActiveManifest(manifestPath, { cwd, setBy: "test" });
|
|
57
|
+
if (setResult.manifest_path !== manifestPath) {
|
|
58
|
+
throw new Error(`set returned wrong path: ${setResult.manifest_path}`);
|
|
59
|
+
}
|
|
60
|
+
if (setResult.set_by !== "test") {
|
|
61
|
+
throw new Error(`set_by mismatch: ${setResult.set_by}`);
|
|
62
|
+
}
|
|
63
|
+
const got = getActiveManifest(cwd);
|
|
64
|
+
if (!got)
|
|
65
|
+
throw new Error("expected pointer, got null");
|
|
66
|
+
if (got.manifest_path !== manifestPath) {
|
|
67
|
+
throw new Error(`get returned wrong path: ${got.manifest_path}`);
|
|
68
|
+
}
|
|
69
|
+
if (!got.set_at.match(/^\d{4}-\d{2}-\d{2}T/)) {
|
|
70
|
+
throw new Error(`set_at not ISO-8601: ${got.set_at}`);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
test("set rejects non-existent path", () => {
|
|
75
|
+
withTmpProject((cwd) => {
|
|
76
|
+
let threw = false;
|
|
77
|
+
try {
|
|
78
|
+
setActiveManifest(join(cwd, "does-not-exist.yaml"), { cwd });
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
threw = true;
|
|
82
|
+
}
|
|
83
|
+
if (!threw)
|
|
84
|
+
throw new Error("expected throw on non-existent path");
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
test("set with skipExistsCheck allows missing path (for tests)", () => {
|
|
88
|
+
withTmpProject((cwd) => {
|
|
89
|
+
const r = setActiveManifest(join(cwd, "future.yaml"), { cwd, skipExistsCheck: true });
|
|
90
|
+
if (!r.manifest_path.endsWith("future.yaml")) {
|
|
91
|
+
throw new Error("path not set");
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
test("clear when not set returns false (idempotent)", () => {
|
|
96
|
+
withTmpProject((cwd) => {
|
|
97
|
+
const r = clearActiveManifest(cwd);
|
|
98
|
+
if (r !== false)
|
|
99
|
+
throw new Error(`expected false, got ${r}`);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
test("set + clear + get returns null", () => {
|
|
103
|
+
withTmpProject((cwd, manifestPath) => {
|
|
104
|
+
setActiveManifest(manifestPath, { cwd });
|
|
105
|
+
const cleared = clearActiveManifest(cwd);
|
|
106
|
+
if (cleared !== true)
|
|
107
|
+
throw new Error("clear should return true after a set");
|
|
108
|
+
const got = getActiveManifest(cwd);
|
|
109
|
+
if (got !== null)
|
|
110
|
+
throw new Error("expected null after clear");
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
test("corrupt JSON throws on get (no silent degradation)", () => {
|
|
114
|
+
withTmpProject((cwd) => {
|
|
115
|
+
const stateDir = join(cwd, ".forge", "state");
|
|
116
|
+
mkdirSync(stateDir, { recursive: true });
|
|
117
|
+
writeFileSync(join(stateDir, "active-manifest.json"), "{ this is not valid json }");
|
|
118
|
+
let threw = false;
|
|
119
|
+
try {
|
|
120
|
+
getActiveManifest(cwd);
|
|
121
|
+
}
|
|
122
|
+
catch (e) {
|
|
123
|
+
threw = true;
|
|
124
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
125
|
+
if (!msg.includes("corrupt")) {
|
|
126
|
+
throw new Error(`expected 'corrupt' in error message, got: ${msg}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (!threw)
|
|
130
|
+
throw new Error("expected throw on corrupt JSON");
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
test("missing required fields throws on get", () => {
|
|
134
|
+
withTmpProject((cwd) => {
|
|
135
|
+
const stateDir = join(cwd, ".forge", "state");
|
|
136
|
+
mkdirSync(stateDir, { recursive: true });
|
|
137
|
+
writeFileSync(join(stateDir, "active-manifest.json"), JSON.stringify({ manifest_path: "x" }));
|
|
138
|
+
let threw = false;
|
|
139
|
+
try {
|
|
140
|
+
getActiveManifest(cwd);
|
|
141
|
+
}
|
|
142
|
+
catch (e) {
|
|
143
|
+
threw = true;
|
|
144
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
145
|
+
if (!msg.includes("missing required fields")) {
|
|
146
|
+
throw new Error(`expected 'missing required fields', got: ${msg}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (!threw)
|
|
150
|
+
throw new Error("expected throw on missing fields");
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
test("atomic write: pointer file ends with newline + valid JSON", () => {
|
|
154
|
+
withTmpProject((cwd, manifestPath) => {
|
|
155
|
+
setActiveManifest(manifestPath, { cwd });
|
|
156
|
+
const path = getActivePointerPath(cwd);
|
|
157
|
+
const raw = readFileSync(path, "utf-8");
|
|
158
|
+
if (!raw.endsWith("\n"))
|
|
159
|
+
throw new Error("pointer file should end with newline");
|
|
160
|
+
JSON.parse(raw); // throws if invalid
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
test("set rejects directory paths (not a regular file)", () => {
|
|
164
|
+
withTmpProject((cwd) => {
|
|
165
|
+
let threw = false;
|
|
166
|
+
try {
|
|
167
|
+
setActiveManifest(cwd, { cwd });
|
|
168
|
+
}
|
|
169
|
+
catch (e) {
|
|
170
|
+
threw = true;
|
|
171
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
172
|
+
if (!msg.includes("not a regular file")) {
|
|
173
|
+
throw new Error(`expected 'not a regular file', got: ${msg}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (!threw)
|
|
177
|
+
throw new Error("expected throw on directory path");
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
test("get throws when pointed-to manifest has been deleted", () => {
|
|
181
|
+
withTmpProject((cwd, manifestPath) => {
|
|
182
|
+
setActiveManifest(manifestPath, { cwd });
|
|
183
|
+
rmSync(manifestPath); // simulate deletion / branch switch
|
|
184
|
+
let threw = false;
|
|
185
|
+
try {
|
|
186
|
+
getActiveManifest(cwd);
|
|
187
|
+
}
|
|
188
|
+
catch (e) {
|
|
189
|
+
threw = true;
|
|
190
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
191
|
+
if (!msg.includes("cannot be read")) {
|
|
192
|
+
throw new Error(`expected 'cannot be read', got: ${msg}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (!threw)
|
|
196
|
+
throw new Error("expected throw on stale pointer");
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
test("get throws when manifest_path is relative in pointer JSON", () => {
|
|
200
|
+
withTmpProject((cwd) => {
|
|
201
|
+
const stateDir = join(cwd, ".forge", "state");
|
|
202
|
+
mkdirSync(stateDir, { recursive: true });
|
|
203
|
+
writeFileSync(join(stateDir, "active-manifest.json"), JSON.stringify({ manifest_path: "relative/path.yaml", set_at: "2026-05-04T00:00:00Z", set_by: "test" }));
|
|
204
|
+
let threw = false;
|
|
205
|
+
try {
|
|
206
|
+
getActiveManifest(cwd);
|
|
207
|
+
}
|
|
208
|
+
catch (e) {
|
|
209
|
+
threw = true;
|
|
210
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
211
|
+
if (!msg.includes("must be absolute")) {
|
|
212
|
+
throw new Error(`expected 'must be absolute', got: ${msg}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (!threw)
|
|
216
|
+
throw new Error("expected throw on relative manifest_path");
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
test("get throws when manifest_path is wrong type (e.g. number)", () => {
|
|
220
|
+
withTmpProject((cwd) => {
|
|
221
|
+
const stateDir = join(cwd, ".forge", "state");
|
|
222
|
+
mkdirSync(stateDir, { recursive: true });
|
|
223
|
+
writeFileSync(join(stateDir, "active-manifest.json"), JSON.stringify({ manifest_path: 42, set_at: "2026-05-04T00:00:00Z", set_by: "test" }));
|
|
224
|
+
let threw = false;
|
|
225
|
+
try {
|
|
226
|
+
getActiveManifest(cwd);
|
|
227
|
+
}
|
|
228
|
+
catch (e) {
|
|
229
|
+
threw = true;
|
|
230
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
231
|
+
if (!msg.includes("wrong types") && !msg.includes("missing required fields")) {
|
|
232
|
+
throw new Error(`expected type-validation error, got: ${msg}`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (!threw)
|
|
236
|
+
throw new Error("expected throw on wrong type");
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
test("set + clear leaves no orphan tmp files", () => {
|
|
240
|
+
withTmpProject((cwd, manifestPath) => {
|
|
241
|
+
setActiveManifest(manifestPath, { cwd });
|
|
242
|
+
clearActiveManifest(cwd);
|
|
243
|
+
const stateDir = join(cwd, ".forge", "state");
|
|
244
|
+
const entries = readdirSync(stateDir);
|
|
245
|
+
const orphans = entries.filter((e) => e.includes(".tmp"));
|
|
246
|
+
if (orphans.length > 0)
|
|
247
|
+
throw new Error(`found orphan tmp files: ${orphans.join(", ")}`);
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
test("set replaces existing pointer (last writer wins)", () => {
|
|
251
|
+
withTmpProject((cwd, manifestPath) => {
|
|
252
|
+
const otherManifest = join(cwd, "other.yaml");
|
|
253
|
+
writeFileSync(otherManifest, "schema_version: \"5\"\n");
|
|
254
|
+
setActiveManifest(manifestPath, { cwd, setBy: "first" });
|
|
255
|
+
setActiveManifest(otherManifest, { cwd, setBy: "second" });
|
|
256
|
+
const got = getActiveManifest(cwd);
|
|
257
|
+
if (!got)
|
|
258
|
+
throw new Error("expected pointer");
|
|
259
|
+
if (got.manifest_path !== otherManifest) {
|
|
260
|
+
throw new Error(`expected second writer to win, got ${got.manifest_path}`);
|
|
261
|
+
}
|
|
262
|
+
if (got.set_by !== "second")
|
|
263
|
+
throw new Error(`set_by should be 'second', got ${got.set_by}`);
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
console.log(`\n=== ${passed} passed, ${failed} failed ===`);
|
|
267
|
+
if (failed > 0) {
|
|
268
|
+
console.log("\nFailures:");
|
|
269
|
+
fails.forEach((f) => console.log(" - " + f));
|
|
270
|
+
process.exit(1);
|
|
271
|
+
}
|
|
272
|
+
process.exit(0);
|