@aperant/framework 0.8.7 → 0.10.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/CHANGELOG.md +142 -0
- package/agents/apt-pr-review-fixer.md +9 -4
- package/agents/apt-verifier.md +6 -3
- package/dist/cli/commands/init.d.mts.map +1 -1
- package/dist/cli/commands/init.mjs +157 -23
- package/dist/cli/commands/init.mjs.map +1 -1
- package/dist/cli/commands/toolchain-detect.d.mts.map +1 -1
- package/dist/cli/commands/toolchain-detect.mjs +44 -0
- package/dist/cli/commands/toolchain-detect.mjs.map +1 -1
- package/dist/cli/commands/triage.d.mts.map +1 -1
- package/dist/cli/commands/triage.mjs +346 -13
- package/dist/cli/commands/triage.mjs.map +1 -1
- package/dist/cli/commands/uninstall.d.mts.map +1 -1
- package/dist/cli/commands/uninstall.mjs +11 -8
- package/dist/cli/commands/uninstall.mjs.map +1 -1
- package/dist/cli/consistency/parse-review.mjs +5 -3
- package/dist/cli/consistency/parse-review.mjs.map +1 -1
- package/dist/cli/consistency/rules/r5-verdict-consistency.d.mts.map +1 -1
- package/dist/cli/consistency/rules/r5-verdict-consistency.mjs +10 -8
- package/dist/cli/consistency/rules/r5-verdict-consistency.mjs.map +1 -1
- package/dist/cli/gate/gates/review-clean.d.mts.map +1 -1
- package/dist/cli/gate/gates/review-clean.mjs +4 -2
- package/dist/cli/gate/gates/review-clean.mjs.map +1 -1
- package/dist/cli/install/mcp-provision.d.mts +26 -0
- package/dist/cli/install/mcp-provision.d.mts.map +1 -0
- package/dist/cli/install/mcp-provision.mjs +281 -0
- package/dist/cli/install/mcp-provision.mjs.map +1 -0
- package/dist/cli/install/mcp-server-specs.d.mts +80 -0
- package/dist/cli/install/mcp-server-specs.d.mts.map +1 -0
- package/dist/cli/install/mcp-server-specs.mjs +102 -0
- package/dist/cli/install/mcp-server-specs.mjs.map +1 -0
- package/dist/cli/install/runtime-detect.d.mts +27 -0
- package/dist/cli/install/runtime-detect.d.mts.map +1 -1
- package/dist/cli/install/runtime-detect.mjs +32 -0
- package/dist/cli/install/runtime-detect.mjs.map +1 -1
- package/dist/cli/install/toml-merge.d.mts +40 -0
- package/dist/cli/install/toml-merge.d.mts.map +1 -0
- package/dist/cli/install/toml-merge.mjs +134 -0
- package/dist/cli/install/toml-merge.mjs.map +1 -0
- package/dist/cli/util/aperant-section.d.mts +12 -0
- package/dist/cli/util/aperant-section.d.mts.map +1 -1
- package/dist/cli/util/aperant-section.mjs +12 -0
- package/dist/cli/util/aperant-section.mjs.map +1 -1
- package/dist/cli/util/copy.d.mts +27 -0
- package/dist/cli/util/copy.d.mts.map +1 -1
- package/dist/cli/util/copy.mjs +49 -5
- package/dist/cli/util/copy.mjs.map +1 -1
- package/dist/cli/util/events-path.d.mts +8 -0
- package/dist/cli/util/events-path.d.mts.map +1 -0
- package/dist/cli/util/events-path.mjs +23 -0
- package/dist/cli/util/events-path.mjs.map +1 -0
- package/dist/cli/util/runtime-capabilities.d.mts +30 -0
- package/dist/cli/util/runtime-capabilities.d.mts.map +1 -0
- package/dist/cli/util/runtime-capabilities.mjs +214 -0
- package/dist/cli/util/runtime-capabilities.mjs.map +1 -0
- package/dist/cli/verify-proof/audit.d.mts +2 -7
- package/dist/cli/verify-proof/audit.d.mts.map +1 -1
- package/dist/cli/verify-proof/audit.mjs +11 -17
- package/dist/cli/verify-proof/audit.mjs.map +1 -1
- package/dist/cli/verify-proof/idl/index.d.mts +12 -1
- package/dist/cli/verify-proof/idl/index.d.mts.map +1 -1
- package/dist/cli/verify-proof/idl/index.mjs +50 -1
- package/dist/cli/verify-proof/idl/index.mjs.map +1 -1
- package/dist/cli/verify-proof/resolver.d.mts.map +1 -1
- package/dist/cli/verify-proof/resolver.mjs +51 -1
- package/dist/cli/verify-proof/resolver.mjs.map +1 -1
- package/dist/cli/verify-proof/runtime-detect.d.mts +2 -29
- package/dist/cli/verify-proof/runtime-detect.d.mts.map +1 -1
- package/dist/cli/verify-proof/runtime-detect.mjs +8 -203
- package/dist/cli/verify-proof/runtime-detect.mjs.map +1 -1
- package/dist/plugin/.claude-plugin/plugin.json +1 -1
- package/dist/plugin/agents/apt-pr-review-fixer.md +9 -4
- package/dist/plugin/agents/apt-verifier.md +6 -3
- package/dist/plugin/skills/apt-pr-review/SKILL.md +106 -15
- package/dist/plugin/skills/apt-setup/SKILL.md +7 -6
- package/dist/plugin/skills/apt-triage/SKILL.md +8 -6
- package/dist/plugin/skills/apt-update/SKILL.md +1 -1
- package/dist/plugin/skills/apt-verify/SKILL.md +1 -1
- package/dist/plugin/skills/apt-verify-proof/SKILL.md +25 -1
- package/drivers/browser/skill/agent-browser/SKILL.md +51 -0
- package/drivers/tauri/README.md +107 -0
- package/drivers/tauri/driver.mjs +88 -0
- package/drivers/tauri/manifest.json +37 -0
- package/drivers/tauri/scripts/tauri-mcp.sh +41 -0
- package/package.json +1 -1
- package/skills/apt-pr-review/SKILL.md +106 -15
- package/skills/apt-setup/SKILL.md +7 -6
- package/skills/apt-triage/SKILL.md +8 -6
- package/skills/apt-update/SKILL.md +1 -1
- package/skills/apt-verify/SKILL.md +1 -1
- package/skills/apt-verify-proof/SKILL.md +25 -1
- package/src/cli/commands/init.mjs +163 -19
- package/src/cli/commands/toolchain-detect.mjs +44 -0
- package/src/cli/commands/triage.mjs +352 -11
- package/src/cli/commands/uninstall.mjs +11 -8
- package/src/cli/consistency/parse-review.mjs +5 -3
- package/src/cli/consistency/rules/r5-verdict-consistency.mjs +14 -9
- package/src/cli/gate/gates/review-clean.mjs +4 -2
- package/src/cli/install/mcp-provision.mjs +293 -0
- package/src/cli/install/mcp-server-specs.mjs +101 -0
- package/src/cli/install/runtime-detect.mjs +36 -0
- package/src/cli/install/toml-merge.mjs +139 -0
- package/src/cli/util/aperant-section.mjs +14 -0
- package/src/cli/util/copy.mjs +53 -8
- package/src/cli/util/events-path.mjs +24 -0
- package/src/cli/util/runtime-capabilities.mjs +216 -0
- package/src/cli/verify-proof/audit.mjs +11 -17
- package/src/cli/verify-proof/idl/index.mjs +49 -11
- package/src/cli/verify-proof/resolver.mjs +49 -2
- package/src/cli/verify-proof/runtime-detect.mjs +11 -204
- package/templates/config.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,148 @@
|
|
|
3
3
|
All notable changes to `@aperant/framework` are documented here. See
|
|
4
4
|
[`VERSIONING.md`](VERSIONING.md) for the per-tier contract.
|
|
5
5
|
|
|
6
|
+
## Unreleased
|
|
7
|
+
|
|
8
|
+
## 0.10.0 — 2026-05-26 — Tauri driver + MCP auto-provision
|
|
9
|
+
|
|
10
|
+
Adds a bundled Tauri verify-proof driver and install-time auto-provisioning
|
|
11
|
+
of the MCP transport each driver needs, so `/apt:verify-proof` works out of
|
|
12
|
+
the box for Tauri (and web) projects with no manual MCP setup.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **Bundled `tauri` verify-proof driver** (`drivers/tauri/`). Same 10-verb
|
|
17
|
+
IDL adapter-shim surface as the electron driver, `transport.mcp_server_id
|
|
18
|
+
"tauri"`, `priority 85` (below electron's 90 — Tauri is rarer), `ga`. The
|
|
19
|
+
verb→tool mapping (the `tauri-plugin-mcp-server` tool surface) lives ONLY
|
|
20
|
+
in the driver + its README (ID-01) — zero workflow-prose edits; a grep
|
|
21
|
+
guard pins the no-`mcp__tauri__*`-in-workflow invariant. The resolver
|
|
22
|
+
already understood `is_tauri`, so it selects the new driver with zero
|
|
23
|
+
resolver edits. The app-side `tauri-plugin-mcp` Rust plugin + `tauri dev
|
|
24
|
+
--features mcp-debug` is a documented PREREQUISITE, never installed by the
|
|
25
|
+
framework.
|
|
26
|
+
- **Install-time MCP/skill auto-provisioning** (`apt-tools init`, and
|
|
27
|
+
`/apt:update` via the same path). Detects the project runtime and, per
|
|
28
|
+
SELECTED host CLI, writes the matching transport TOFU/non-clobbering:
|
|
29
|
+
- claude → JSON-merge the MCP server into project-root `.mcp.json`
|
|
30
|
+
(`mcpServers.<id>`).
|
|
31
|
+
- codex → upsert a `[mcp_servers.<id>]` table into project-root
|
|
32
|
+
`.codex/config.toml` (dependency-free minimal TOML writer).
|
|
33
|
+
- electron / tauri projects get the matching MCP server (tauri via a
|
|
34
|
+
generated `scripts/tauri-mcp.sh` launcher, since its npm bin ships
|
|
35
|
+
without a shebang); web frontends get the browser driver's declared
|
|
36
|
+
`agent-browser` skill copied into each host skill dir
|
|
37
|
+
(`.claude/skills/`, `.agents/skills/`) — NOT a puppeteer MCP server.
|
|
38
|
+
A pre-existing server id, config table, skill file, or launcher is never
|
|
39
|
+
overwritten (protects a hand-authored `SCREENSHOT_ENCRYPTION_KEY` and a
|
|
40
|
+
user-edited skill). Each action is audited to
|
|
41
|
+
`.aperant/events/{date}.jsonl`. Opt out with `verification.provision_mcp:
|
|
42
|
+
false` (default `true`). v1 host writers: claude + codex.
|
|
43
|
+
|
|
44
|
+
### Out of scope (documented)
|
|
45
|
+
|
|
46
|
+
- The Rust/Tauri app-side plugin (`tauri-plugin-mcp`) — a project prereq.
|
|
47
|
+
- Puppeteer MCP server provisioning — web projects get the `agent-browser`
|
|
48
|
+
skill instead; the browser driver manifest's `mcp_server_id` is untouched.
|
|
49
|
+
- The `agent-browser` CLI binary, and generating/persisting
|
|
50
|
+
`SCREENSHOT_ENCRYPTION_KEY` — both documented prereqs, not auto-provisioned.
|
|
51
|
+
|
|
52
|
+
## 0.9.1 — 2026-05-25 — G42 Phase 1: close deferred ship/merge bypass
|
|
53
|
+
|
|
54
|
+
**Breaking (behavior):** Blocking review findings tagged `deferred` no longer
|
|
55
|
+
silently clear the ship gate. Only `status: "fixed"` clears a `critical` or
|
|
56
|
+
`major` finding. The `--force --reason "<why>"` override (audit-logged to
|
|
57
|
+
`.aperant/events/`) remains the sole intentional bypass.
|
|
58
|
+
|
|
59
|
+
### Fixed
|
|
60
|
+
|
|
61
|
+
- **`review-clean` gate now blocks `critical`/`major` findings tagged `deferred`.** Previously,
|
|
62
|
+
tagging a blocking finding `deferred` (any variant: `deferred`, `deferred-per-spec`,
|
|
63
|
+
`deferred-followup`, `deferred-out-of-scope`) silently cleared the ship gate, allowing a
|
|
64
|
+
critical safety defect to ship as ✓MERGED. The gate now only accepts `status: "fixed"` as a
|
|
65
|
+
clearing condition for blocking severities. A deliberate override is still possible via
|
|
66
|
+
`--force --reason "<why>"` (audit-logged). (G42 Phase 1)
|
|
67
|
+
- **`r5-verdict-consistency` rule no longer exempts `approved-with-notes` verdict from blocking
|
|
68
|
+
finding check.** `approved-with-notes` was previously an explicit escape hatch that passed R5
|
|
69
|
+
regardless of unresolved criticals. It now fails R5 when any unresolved `critical` or `major`
|
|
70
|
+
finding exists. The check also extends to `major` severity (previously only `critical` was
|
|
71
|
+
checked). (G42 Phase 1)
|
|
72
|
+
- **`apt-verify` completeness carve-out adds severity floor.** The verifier no longer exempts a
|
|
73
|
+
`critical` or `major` deferred behavior from Completeness scoring — such findings are now
|
|
74
|
+
treated as a Completeness gap. (G42 Phase 1)
|
|
75
|
+
- **`apt-verifier` agent instruction updated.** The verifier agent doc no longer instructs agents
|
|
76
|
+
to use `approved-with-notes` as an escape hatch for unresolved critical/major review findings;
|
|
77
|
+
the sole bypass is the audited `--force --reason` gate override. (G42 Phase 1)
|
|
78
|
+
- **`init` stamps the APT block into every selected runtime's instruction
|
|
79
|
+
file.** Previously init picked exactly ONE instruction file (CLAUDE.md if
|
|
80
|
+
present, else AGENTS.md) regardless of which runtimes were selected, so a
|
|
81
|
+
`--claude --codex` user got the skill catalog in CLAUDE.md only and native
|
|
82
|
+
Codex (which reads AGENTS.md) never saw it; `--gemini` users never got a
|
|
83
|
+
GEMINI.md block at all. `RuntimeDescriptor` now carries a nullable
|
|
84
|
+
`nativeInstructionFile` (claude → CLAUDE.md, codex/opencode → AGENTS.md,
|
|
85
|
+
gemini → GEMINI.md; the other 11 runtimes install to their own surface and
|
|
86
|
+
map to `null`). init resolves the deduplicated target set from the selected
|
|
87
|
+
runtimes, runs an all-target foreign-row preflight (any offending target →
|
|
88
|
+
exit 2, NOTHING written), then writes every target. The result envelope gains
|
|
89
|
+
a plural `instructionFiles[]` array; the singular `instructionFile` stays as a
|
|
90
|
+
backwards-compat alias = first entry. The single-runtime fallback is
|
|
91
|
+
preserved: `--claude` against a project with a pre-existing AGENTS.md (and no
|
|
92
|
+
CLAUDE.md) still writes AGENTS.md.
|
|
93
|
+
- **`uninstall` strips the APT block from GEMINI.md too.** `INSTRUCTION_FILES`
|
|
94
|
+
expanded to `['CLAUDE.md', 'AGENTS.md', 'GEMINI.md']` so a `--gemini` install
|
|
95
|
+
no longer leaves a stale managed block behind.
|
|
96
|
+
- **The chat-agent instruction loader dedupes identical APT blocks.**
|
|
97
|
+
`loadInstructionFiles` (packages/core) now loads a given verbatim
|
|
98
|
+
`<!-- APT:framework-* -->` block once even when it appears in multiple walk-up
|
|
99
|
+
files (the post-fix normal state for multi-runtime users) — preventing the
|
|
100
|
+
skill catalog from rendering twice in the system prompt. Narrow: only
|
|
101
|
+
identical managed blocks are deduped; non-managed content and distinct blocks
|
|
102
|
+
are preserved.
|
|
103
|
+
|
|
104
|
+
## 0.9.0 — 2026-05-25 — github-issues triage backend
|
|
105
|
+
|
|
106
|
+
Replaces the v1 `github-issues` triage stub with a working `gh`-CLI adapter,
|
|
107
|
+
so projects on `task_tracking.backend: "github-issues"` get real GitHub
|
|
108
|
+
issues synced to triage state.
|
|
109
|
+
|
|
110
|
+
### Added
|
|
111
|
+
|
|
112
|
+
- **`github-issues` triage backend is now implemented.** The first triage
|
|
113
|
+
mirror CREATES a GitHub issue via `gh issue create` (`triage: <task-id>`,
|
|
114
|
+
labelled `<category>` + `<vocab>:<state>`); subsequent transitions UPDATE
|
|
115
|
+
the SAME issue (`gh issue edit` label swap); a transition to `wontfix`
|
|
116
|
+
CLOSES it as "not planned" (`gh issue close --reason "not planned"`). The
|
|
117
|
+
issue pointer (`github.{issue_number, issue_url, repo}`) persists on
|
|
118
|
+
`triage.json`, so transitions are idempotent — no duplicate issues. Honors
|
|
119
|
+
`task_tracking.tracker_label_vocabulary` (absent/`"default"` →
|
|
120
|
+
`triage:<state>`; otherwise `<vocab>:<state>`). Transport is the `gh` CLI
|
|
121
|
+
via `execFileSync` arg-arrays (no shell-string interpolation), inheriting
|
|
122
|
+
the user's existing `gh auth` — zero new credential plumbing. When `gh` is
|
|
123
|
+
missing / unauthenticated / there is no resolvable git remote, the adapter
|
|
124
|
+
returns a structured error (`reason: gh-unavailable | gh-unauthenticated |
|
|
125
|
+
no-remote | gh-command-failed`) and exits non-zero — never a crash or
|
|
126
|
+
stacktrace. Label operations are best-effort: an unknown label degrades to
|
|
127
|
+
a `warnings[]` entry while the open/closed state change still lands. A
|
|
128
|
+
deterministic `APT_TRIAGE_GH_FIXTURE` test hook (mirroring `pr-review.mjs`'s
|
|
129
|
+
`APT_GH_FIXTURE_JSON`) keeps the unit tests hermetic — no real network.
|
|
130
|
+
The `app-inbox` backend remains a not-implemented stub (out of scope,
|
|
131
|
+
post-app-launch).
|
|
132
|
+
|
|
133
|
+
### Changed
|
|
134
|
+
|
|
135
|
+
- **`## User Outcomes` coverage gate is now a HARD BLOCK (cutover from the
|
|
136
|
+
0.8.x warn-only grace window).** As telegraphed in the 0.8.6 release notes
|
|
137
|
+
("v0.9.0 will **HARD BLOCK** specs that [lack `## User Outcomes`]"), the
|
|
138
|
+
`coverage-check` gate flips from `warn` to `block` for STANDARD/DEEP plans
|
|
139
|
+
whose spec is missing the `## User Outcomes` section, starting this 0.9.0
|
|
140
|
+
release. The version-gated logic already lived in
|
|
141
|
+
`userOutcomesGateResult` (warn in 0.8.x, block in 0.9.0+); bumping the
|
|
142
|
+
kernel to 0.9.0 activates it. Refactor-only specs use the
|
|
143
|
+
`_No user-observable changes — pure refactor._` empty-with-note escape to
|
|
144
|
+
pass. QUICK-track tasks remain exempt (Fast Path Guarantee). The
|
|
145
|
+
`migration_deadline` advisory note (only emitted during the warn window) is
|
|
146
|
+
no longer surfaced — the deadline has passed.
|
|
147
|
+
|
|
6
148
|
## 0.8.7 — 2026-05-24 — Codex parity: drift false-positive fix + agent inlining
|
|
7
149
|
|
|
8
150
|
Brings the Codex runtime to parity with Claude Code on two fronts.
|
|
@@ -71,9 +71,14 @@ The only valid reasons to skip a finding:
|
|
|
71
71
|
|
|
72
72
|
## Coordination Protocol
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
**Fixers run SEQUENTIALLY, never concurrently (FRAMEWORK-BUG-006).** At most one fixer touches the
|
|
75
|
+
shared worktree at any moment — the orchestrator spawns fixer-1, waits for it to return and audits it,
|
|
76
|
+
THEN spawns fixer-2, and so on. You are the ONLY fixer touching this worktree right now. `[PARALLEL_FIXERS]`
|
|
77
|
+
is always `false`; do NOT assume a sibling fixer is editing, staging, or committing at the same time.
|
|
78
|
+
Concurrent `git add`/`git commit` against a shared index either sweeps another fixer's staged files
|
|
79
|
+
into your commit or clobbers their working-tree edits — which is exactly the race this rule prevents.
|
|
80
|
+
|
|
81
|
+
1. **Only edit files in your `[FIX_ASSIGNMENT]`** — do NOT touch files outside your assigned group (the grouping keeps fixes scoped and the history clean)
|
|
77
82
|
2. **If a finding spans files across groups** — fix only the parts in your group, note the cross-group dependency in your report
|
|
78
83
|
3. **If you discover NEW issues** while fixing — note them in your report but do NOT fix them (they aren't validated)
|
|
79
84
|
|
|
@@ -89,7 +94,7 @@ Findings with severity `critical`:
|
|
|
89
94
|
|
|
90
95
|
## Report Format
|
|
91
96
|
|
|
92
|
-
Write to `[REVIEW_DIR]/iterations/[N]/fixes-applied-[FIXER_NAME].md` (BUG-030 canonical naming — the audit gate resolves `fixes-applied-${FIXER_NAME}.md` ahead of the legacy `fixes-applied.md
|
|
97
|
+
Write to `[REVIEW_DIR]/iterations/[N]/fixes-applied-[FIXER_NAME].md` (BUG-030 canonical naming — the audit gate resolves `fixes-applied-${FIXER_NAME}.md` ahead of the legacy `fixes-applied.md`. Each fixer owns its own report file; this per-fixer path stays correct now that fixers run sequentially — FRAMEWORK-BUG-006):
|
|
93
98
|
|
|
94
99
|
```markdown
|
|
95
100
|
# Fix Report — Iteration [N]
|
package/agents/apt-verifier.md
CHANGED
|
@@ -105,9 +105,12 @@ Either shape is accepted:
|
|
|
105
105
|
|
|
106
106
|
Gate G8 (`consistency-check`) enforces this on ship via rules R4
|
|
107
107
|
(finding-propagation) and R5 (verdict-consistency):
|
|
108
|
-
- `verdict: "approved"`
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
- `verdict: "approved"` or `verdict: "approved-with-notes"` is incompatible
|
|
109
|
+
with any unresolved `critical` or `major` review finding (G42 Phase 1).
|
|
110
|
+
Neither verdict escapes R5 for blocking-severity findings. The sole bypass
|
|
111
|
+
is `--force --reason "<why>"` on the gate CLI (audit-logged). For
|
|
112
|
+
non-blocking findings (suggestion/note/info/nitpick), cite them in
|
|
113
|
+
`findings.notes` and use `approved-with-notes`.
|
|
111
114
|
- `verdict: "rejected"` requires at least one cited finding (rejected
|
|
112
115
|
without evidence is treated as drift).
|
|
113
116
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.mts","sourceRoot":"","sources":["../../../src/cli/commands/init.mjs"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"init.d.mts","sourceRoot":"","sources":["../../../src/cli/commands/init.mjs"],"names":[],"mappings":"AA8EA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wCAHW,MAAM,EAAE,GACN;IAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;IAAC,iBAAiB,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,GAAC,IAAI,CAAC;IAAC,WAAW,EAAE,OAAO,CAAC;IAAC,aAAa,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,GAAC,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,CAAC,GAAC,CAAC,GAAC,CAAC,GAAC,CAAC,CAAC;IAAC,mBAAmB,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,OAAO,CAAC;IAAC,aAAa,EAAE,OAAO,CAAC;IAAC,qBAAqB,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,CAmE1S;AAwzBD;;;;;;;GAOG;AACH,8CAHW,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,WACnB,cAAc,0BAoDxB;AAoED;;;;;;GAMG;AACH,oCAJW,MAAM,cACN,MAAM,EAAE,GACN,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAgN3D;;;;;cAvoCa,MAAM,EAAE;;;;qBACR,CAAC,GAAC,CAAC,GAAC,CAAC,GAAC,CAAC;;;;gBACP,MAAM,GAAC,MAAM;;;;4BACb,MAAM,EAAE;;;;aACR,OAAO;;;;mBACP,OAAO;eACP,OAAO,0BAA0B,EAAE,eAAe;;;;;;;;;aAErC,MAAM;eAAS,OAAO;cAAQ,MAAM;iBAAW,MAAM;;;;;;aAErD,MAAM;eAAS,OAAO;gBAAU,MAAM;;;;;;kBAEvC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;0BAAoB,OAAO,EAAE;2BAAqB,OAAO,EAAE;oBAAc,OAAO,EAAE"}
|
|
@@ -22,7 +22,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
22
22
|
import { detectMode } from '../bootstrap/detect-mode.mjs';
|
|
23
23
|
import { deriveSidecar } from '../bootstrap/product-context-sidecar.mjs';
|
|
24
24
|
import { assertConfigWriteAllowed } from '../config/guard.mjs';
|
|
25
|
-
import { AUDIT_001_LOCAL_FIELDS, migrateDeferredFieldsToLocalConfig, writeLocalConfigField, } from '../config/load.mjs';
|
|
25
|
+
import { AUDIT_001_LOCAL_FIELDS, loadMergedProjectConfig, migrateDeferredFieldsToLocalConfig, writeLocalConfigField, } from '../config/load.mjs';
|
|
26
26
|
import { migrateConfigSchema, setAt } from '../config/schema-migrate.mjs';
|
|
27
27
|
import { resolveSharePolicy } from '../config/share-policy.mjs';
|
|
28
28
|
import { buildAperantGitignoreTemplate } from '../config/upgrade-gitignore.mjs';
|
|
@@ -30,6 +30,7 @@ import { detectPublicRemote } from '../git/remote.mjs';
|
|
|
30
30
|
import { detectArchetype } from '../install/archetype.mjs';
|
|
31
31
|
import { installKernel } from '../install/install-kernel.mjs';
|
|
32
32
|
import { MANIFEST_FILENAME } from '../install/manifest.mjs';
|
|
33
|
+
import { provisionMcpConfig } from '../install/mcp-provision.mjs';
|
|
33
34
|
import { discoverCanonicalFiles, runInstall, writeLastInstallSummary, } from '../install/pipeline.mjs';
|
|
34
35
|
import { getRuntime, RUNTIMES, suggestRuntimes } from '../install/runtime-detect.mjs';
|
|
35
36
|
import { runLegacyCleanup } from '../install/runtime-migrate.mjs';
|
|
@@ -38,7 +39,7 @@ import { detectAgentRuntime } from '../util/agent-runtime.mjs';
|
|
|
38
39
|
import { buildAptSection } from '../util/aperant-section.mjs';
|
|
39
40
|
import { atomicWriteJson } from '../util/atomic-write.mjs';
|
|
40
41
|
import { aperantBanner } from '../util/banner.mjs';
|
|
41
|
-
import { copyDirRecursive, injectInstructionFile } from '../util/copy.mjs';
|
|
42
|
+
import { checkInstructionFile, copyDirRecursive, injectInstructionFile } from '../util/copy.mjs';
|
|
42
43
|
import { withFileLock } from '../util/fs-lock.mjs';
|
|
43
44
|
import { emitInitConfigHint } from '../util/init-hints.mjs';
|
|
44
45
|
import { err, exitWith, ok, rawText } from '../util/result.mjs';
|
|
@@ -1183,6 +1184,21 @@ export async function cmdInit(projectDir, extraArgs = []) {
|
|
|
1183
1184
|
if (envelope?.__earlyExit) {
|
|
1184
1185
|
return envelope.__earlyExit;
|
|
1185
1186
|
}
|
|
1187
|
+
// Auto-provision host MCP-server config + the agent-browser skill for
|
|
1188
|
+
// the detected runtime (C38). Runs on BOTH the fresh install AND the
|
|
1189
|
+
// no-drift /apt:update upgrade path (both reach finishInit). finishInit
|
|
1190
|
+
// has already written .aperant/config.json from the template, so the
|
|
1191
|
+
// merged config carries verification.provision_mcp (default true) — the
|
|
1192
|
+
// opt-out gate. TOFU/non-clobbering; never throws.
|
|
1193
|
+
const mergedConfig = loadMergedProjectConfig(installTarget);
|
|
1194
|
+
const mcpProvision = provisionMcpConfig({
|
|
1195
|
+
targetDir: installTarget,
|
|
1196
|
+
selectedRuntimes: effectiveAnswers.runtimes,
|
|
1197
|
+
config: mergedConfig,
|
|
1198
|
+
frameworkRoot: canonicalRoot,
|
|
1199
|
+
version,
|
|
1200
|
+
});
|
|
1201
|
+
envelope.mcp_provision = mcpProvision;
|
|
1186
1202
|
if (interactiveTty) {
|
|
1187
1203
|
await renderOutro(effectiveAnswers, results, version, kernelResult);
|
|
1188
1204
|
// TTY mode: envelope was persisted to disk + suppressed from stdout
|
|
@@ -1325,6 +1341,69 @@ async function renderOutro(answers, results, version, kernelResult) {
|
|
|
1325
1341
|
clack.note('Relaunch Claude Code / Codex / your AI CLI in this directory\nso the new .claude/ + .codex/ surfaces are picked up.', 'Activate');
|
|
1326
1342
|
clack.outro(`Installed ${fileCount} files · v${version} · ${xverifyCopy}`);
|
|
1327
1343
|
}
|
|
1344
|
+
/**
|
|
1345
|
+
* Resolve the deduplicated set of root instruction-file paths the selected
|
|
1346
|
+
* runtimes read. Each chosen runtime contributes its `nativeInstructionFile`
|
|
1347
|
+
* (claude→CLAUDE.md, codex/opencode→AGENTS.md, gemini→GEMINI.md); runtimes
|
|
1348
|
+
* mapped to `null` install to their own surface and contribute nothing.
|
|
1349
|
+
* Filenames are deduplicated (codex + opencode both map to AGENTS.md → one
|
|
1350
|
+
* target).
|
|
1351
|
+
*
|
|
1352
|
+
* Resolution rules:
|
|
1353
|
+
* - Multiple distinct targets (e.g. `--claude --codex`): write all of them
|
|
1354
|
+
* verbatim — that is the whole point of the fix (AC1, AC2, AC5).
|
|
1355
|
+
* - Exactly one resolved target that does NOT exist AND it is CLAUDE.md, but
|
|
1356
|
+
* AGENTS.md already exists (the `--claude` with a pre-seeded AGENTS.md and
|
|
1357
|
+
* no CLAUDE.md case, Risk 4 / AC9): redirect to the existing AGENTS.md
|
|
1358
|
+
* rather than creating an orphan CLAUDE.md. This is safe ONLY for the
|
|
1359
|
+
* claude runtime because native Claude Code reads AGENTS.md as well as
|
|
1360
|
+
* CLAUDE.md. The redirect is NOT applied for gemini (reads only GEMINI.md)
|
|
1361
|
+
* or codex/opencode (read only AGENTS.md) — redirecting those to an
|
|
1362
|
+
* unrelated existing file would reintroduce the very visibility gap this
|
|
1363
|
+
* fix closes, so their absent target is simply created.
|
|
1364
|
+
* - Zero resolved targets (no selected runtime maps to an instruction file):
|
|
1365
|
+
* fall back to the legacy heuristic — CLAUDE.md if present, else AGENTS.md
|
|
1366
|
+
* if present, else a new CLAUDE.md.
|
|
1367
|
+
*
|
|
1368
|
+
* @param {string[]} chosenIds
|
|
1369
|
+
* @param {string} targetDir
|
|
1370
|
+
* @returns {string[]} absolute instruction-file paths, deduplicated
|
|
1371
|
+
*/
|
|
1372
|
+
function resolveInstructionTargets(chosenIds, targetDir) {
|
|
1373
|
+
const filenames = [];
|
|
1374
|
+
for (const id of chosenIds) {
|
|
1375
|
+
const runtime = getRuntime(id);
|
|
1376
|
+
const file = runtime?.nativeInstructionFile;
|
|
1377
|
+
if (file && !filenames.includes(file))
|
|
1378
|
+
filenames.push(file);
|
|
1379
|
+
}
|
|
1380
|
+
const claudeMdPath = join(targetDir, 'CLAUDE.md');
|
|
1381
|
+
const agentsMdPath = join(targetDir, 'AGENTS.md');
|
|
1382
|
+
const resolved = filenames.map((name) => join(targetDir, name));
|
|
1383
|
+
if (resolved.length === 0) {
|
|
1384
|
+
// No selected runtime maps to an instruction file — legacy heuristic.
|
|
1385
|
+
if (existsSync(claudeMdPath))
|
|
1386
|
+
return [claudeMdPath];
|
|
1387
|
+
if (existsSync(agentsMdPath))
|
|
1388
|
+
return [agentsMdPath];
|
|
1389
|
+
return [claudeMdPath];
|
|
1390
|
+
}
|
|
1391
|
+
if (resolved.length === 1 && !existsSync(resolved[0])) {
|
|
1392
|
+
// Single registry target that doesn't exist yet. The ONLY safe redirect
|
|
1393
|
+
// is claude→AGENTS.md (Risk 4 / AC9): native Claude Code reads AGENTS.md
|
|
1394
|
+
// too, so when CLAUDE.md is absent but AGENTS.md exists (e.g. a prior
|
|
1395
|
+
// codex install), writing AGENTS.md still serves claude and avoids an
|
|
1396
|
+
// orphan CLAUDE.md. gemini/codex/opencode read only their own native
|
|
1397
|
+
// file — redirecting them to an unrelated existing file would hide the
|
|
1398
|
+
// catalog from their CLI, so we just create their target.
|
|
1399
|
+
if (resolved[0] === claudeMdPath && existsSync(agentsMdPath))
|
|
1400
|
+
return [agentsMdPath];
|
|
1401
|
+
return resolved;
|
|
1402
|
+
}
|
|
1403
|
+
// One or more registry targets, at least one exists OR multiple distinct
|
|
1404
|
+
// targets — write the registry-resolved set verbatim.
|
|
1405
|
+
return resolved;
|
|
1406
|
+
}
|
|
1328
1407
|
/**
|
|
1329
1408
|
* Synchronous tail of init — runs after the runtime-install promise
|
|
1330
1409
|
* resolves. Performs all .aperant/ + Claude-hook + remote-warning work
|
|
@@ -1488,18 +1567,19 @@ function finishInit(targetDir, installTarget, canonicalRoot, version, chosenIds,
|
|
|
1488
1567
|
if (existsSync(installedContextSrc)) {
|
|
1489
1568
|
copyDirRecursive(installedContextSrc, join(aperantDir, 'context', 'installed'));
|
|
1490
1569
|
}
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1570
|
+
// Resolve every distinct instruction file the selected runtimes read, then
|
|
1571
|
+
// preflight ALL of them before writing ANY — so a single foreign-row abort
|
|
1572
|
+
// leaves no file partially written (US-03 / AC3). Each chosen runtime maps
|
|
1573
|
+
// to its `nativeInstructionFile` (claude→CLAUDE.md, codex/opencode→AGENTS.md,
|
|
1574
|
+
// gemini→GEMINI.md); null-mapped runtimes install to their own surface and
|
|
1575
|
+
// contribute no target. When the selected runtimes resolve to zero targets,
|
|
1576
|
+
// or none of the resolved targets exist on disk, fall back to the legacy
|
|
1577
|
+
// "pick the existing file" heuristic so e.g. `--claude` with a pre-seeded
|
|
1578
|
+
// AGENTS.md (and no CLAUDE.md) still writes AGENTS.md (Risk 4 / AC9).
|
|
1579
|
+
const instructionTargets = resolveInstructionTargets(chosenIds, targetDir);
|
|
1500
1580
|
// FRAMEWORK-BUG-023 — generate the section from discoverSkills before
|
|
1501
1581
|
// handing it to the writer. The writer also runs a foreign-row defensive
|
|
1502
|
-
// check; if rows present in
|
|
1582
|
+
// check; if rows present in any file aren't in the generated set we
|
|
1503
1583
|
// signal exit-2 via the __earlyExit sentinel so cmdInit can route the
|
|
1504
1584
|
// envelope through exitWith() without writing a malformed install.
|
|
1505
1585
|
const aptSection = buildAptSection({
|
|
@@ -1507,26 +1587,75 @@ function finishInit(targetDir, installTarget, canonicalRoot, version, chosenIds,
|
|
|
1507
1587
|
targetDir,
|
|
1508
1588
|
cli: detectAgentRuntime(),
|
|
1509
1589
|
});
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1590
|
+
// Preflight pass — check every target with the write-free helper; collect
|
|
1591
|
+
// all aborting targets so the error lists every offender, not just the first.
|
|
1592
|
+
const abortingTargets = [];
|
|
1593
|
+
for (const filePath of instructionTargets) {
|
|
1594
|
+
const check = checkInstructionFile(filePath, { aptSection, force: forceOverwriteCatalog });
|
|
1595
|
+
if (check.status === 'aborted-foreign-rows') {
|
|
1596
|
+
abortingTargets.push({ path: basename(filePath), foreignRowIds: check.foreignRowIds });
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
if (abortingTargets.length > 0) {
|
|
1600
|
+
const allForeignRowIds = [...new Set(abortingTargets.flatMap((t) => t.foreignRowIds))];
|
|
1601
|
+
const fileList = abortingTargets.map((t) => t.path).join(', ');
|
|
1602
|
+
return {
|
|
1603
|
+
__earlyExit: exitWith({
|
|
1604
|
+
ok: false,
|
|
1605
|
+
status: 'error',
|
|
1606
|
+
command: 'init',
|
|
1607
|
+
code: 'instruction-file.foreign-rows',
|
|
1608
|
+
message: `${fileList} contains skill rows not present in the installed framework: ` +
|
|
1609
|
+
`${allForeignRowIds.join(', ')}. ` +
|
|
1610
|
+
'Re-run with --force-overwrite-catalog to regenerate anyway, or remove the foreign rows manually.',
|
|
1611
|
+
foreignRowIds: allForeignRowIds,
|
|
1612
|
+
instructionFile: { path: abortingTargets[0].path, status: 'aborted-foreign-rows' },
|
|
1613
|
+
instructionFiles: abortingTargets.map((t) => ({
|
|
1614
|
+
path: t.path,
|
|
1615
|
+
status: 'aborted-foreign-rows',
|
|
1616
|
+
})),
|
|
1617
|
+
}, 2),
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
1620
|
+
// Write pass — no target aborted in the preflight, so write all.
|
|
1621
|
+
const instructionFiles = instructionTargets.map((filePath) => {
|
|
1622
|
+
const outcome = injectInstructionFile(filePath, {
|
|
1623
|
+
aptSection,
|
|
1624
|
+
force: forceOverwriteCatalog,
|
|
1625
|
+
});
|
|
1626
|
+
return {
|
|
1627
|
+
path: basename(filePath),
|
|
1628
|
+
status: outcome.status,
|
|
1629
|
+
foreignRowIds: outcome.foreignRowIds,
|
|
1630
|
+
};
|
|
1513
1631
|
});
|
|
1514
|
-
if
|
|
1632
|
+
// TOCTOU guard: if a file gained foreign rows BETWEEN the preflight check and
|
|
1633
|
+
// the write (e.g. another process wrote to it), surface the same error envelope
|
|
1634
|
+
// the preflight uses rather than silently accepting partial writes.
|
|
1635
|
+
const writtenAborts = instructionFiles.filter((f) => f.status === 'aborted-foreign-rows');
|
|
1636
|
+
if (writtenAborts.length > 0) {
|
|
1637
|
+
const allForeignRowIds = [...new Set(writtenAborts.flatMap((f) => f.foreignRowIds ?? []))];
|
|
1638
|
+
const fileList = writtenAborts.map((f) => f.path).join(', ');
|
|
1515
1639
|
return {
|
|
1516
1640
|
__earlyExit: exitWith({
|
|
1517
1641
|
ok: false,
|
|
1518
1642
|
status: 'error',
|
|
1519
1643
|
command: 'init',
|
|
1520
1644
|
code: 'instruction-file.foreign-rows',
|
|
1521
|
-
message: `${
|
|
1522
|
-
`${
|
|
1645
|
+
message: `${fileList} contains skill rows not present in the installed framework: ` +
|
|
1646
|
+
`${allForeignRowIds.join(', ')}. ` +
|
|
1523
1647
|
'Re-run with --force-overwrite-catalog to regenerate anyway, or remove the foreign rows manually.',
|
|
1524
|
-
foreignRowIds:
|
|
1525
|
-
instructionFile:
|
|
1648
|
+
foreignRowIds: allForeignRowIds,
|
|
1649
|
+
instructionFile: { path: writtenAborts[0].path, status: 'aborted-foreign-rows' },
|
|
1650
|
+
instructionFiles: writtenAborts.map((f) => ({
|
|
1651
|
+
path: f.path,
|
|
1652
|
+
status: 'aborted-foreign-rows',
|
|
1653
|
+
})),
|
|
1526
1654
|
}, 2),
|
|
1527
1655
|
};
|
|
1528
1656
|
}
|
|
1529
|
-
|
|
1657
|
+
// Primary entry drives the backwards-compat singular alias + structure key.
|
|
1658
|
+
const primaryInstructionFile = instructionFiles[0];
|
|
1530
1659
|
const installedClaude = chosenIds.includes('claude') || existsSync(join(targetDir, '.claude'));
|
|
1531
1660
|
const precompactResult = installedClaude
|
|
1532
1661
|
? installPrecompactHook(targetDir)
|
|
@@ -1560,9 +1689,11 @@ function finishInit(targetDir, installTarget, canonicalRoot, version, chosenIds,
|
|
|
1560
1689
|
'.aperant/events/': 'event log (git-ignored)',
|
|
1561
1690
|
'.aperant/prompts/': 'agent prompt templates',
|
|
1562
1691
|
'.aperant/install/last-install.json': 'cross-runtime install summary',
|
|
1563
|
-
[basename(instructionFile)]: `APT section ${injectionResult}`,
|
|
1564
1692
|
'.claude/settings.local.json': `PreCompact hook ${precompactResult}; SessionStart hook ${sessionStartResult}`,
|
|
1565
1693
|
};
|
|
1694
|
+
for (const f of instructionFiles) {
|
|
1695
|
+
structure[f.path] = `APT section ${f.status}`;
|
|
1696
|
+
}
|
|
1566
1697
|
if (kernelResult) {
|
|
1567
1698
|
structure['.aperant/deps/'] =
|
|
1568
1699
|
kernelResult.status === 'installed'
|
|
@@ -1601,7 +1732,10 @@ function finishInit(targetDir, installTarget, canonicalRoot, version, chosenIds,
|
|
|
1601
1732
|
framework_version: version,
|
|
1602
1733
|
configCreated,
|
|
1603
1734
|
configMigrated,
|
|
1604
|
-
|
|
1735
|
+
// Plural contract (ID-03): one entry per instruction file written.
|
|
1736
|
+
// `instructionFile` (singular) is a backwards-compat alias = first entry.
|
|
1737
|
+
instructionFiles,
|
|
1738
|
+
instructionFile: primaryInstructionFile,
|
|
1605
1739
|
stateCreated,
|
|
1606
1740
|
seedsCreated,
|
|
1607
1741
|
publicRemoteWarning,
|