@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
|
@@ -20,6 +20,7 @@ gates: []
|
|
|
20
20
|
config_keys:
|
|
21
21
|
- verification.open_report
|
|
22
22
|
- verification.report_formats
|
|
23
|
+
- verification.provision_mcp
|
|
23
24
|
---
|
|
24
25
|
|
|
25
26
|
# Proof Verification
|
|
@@ -104,13 +105,36 @@ disable the walk entirely (root-only behaviour, backwards compat).
|
|
|
104
105
|
The `verification.runtimes[]` override stays the final escape hatch
|
|
105
106
|
and is now usually unnecessary because the walk runs by default.
|
|
106
107
|
|
|
107
|
-
### Bundled drivers (
|
|
108
|
+
### Bundled drivers (5)
|
|
108
109
|
|
|
109
110
|
- **browser** (priority=100, ga) — Next.js / vanilla web frontends.
|
|
110
111
|
- **electron** (priority=90, ga) — Electron desktop apps.
|
|
112
|
+
- **tauri** (priority=85, ga) — Tauri desktop apps.
|
|
111
113
|
- **cli** (priority=60, ga) — headless CI, output-shape assertions.
|
|
112
114
|
- **api** (priority=50, ga) — HTTP / JSON-API regression tests.
|
|
113
115
|
|
|
116
|
+
### Install-time MCP provisioning (verification.provision_mcp)
|
|
117
|
+
|
|
118
|
+
`apt-tools init` (and `/apt:update`, which routes through the same path)
|
|
119
|
+
auto-provisions the MCP transport a driver needs into each SELECTED host
|
|
120
|
+
CLI's config, so verify-proof works at the prompt with no manual MCP
|
|
121
|
+
setup. It detects the project runtime and, per host:
|
|
122
|
+
|
|
123
|
+
- **claude** → JSON-merges the server into the project-root `.mcp.json`
|
|
124
|
+
(`mcpServers.<id>`).
|
|
125
|
+
- **codex** → upserts a `[mcp_servers.<id>]` table into the project-root
|
|
126
|
+
`.codex/config.toml`.
|
|
127
|
+
|
|
128
|
+
Electron and Tauri projects get the matching MCP server; web frontends
|
|
129
|
+
get the browser driver's declared `agent-browser` skill copied into the
|
|
130
|
+
host skill dir (`.claude/skills/` / `.agents/skills/`) — its `agent-browser`
|
|
131
|
+
CLI is a documented one-time prereq, the same class as the
|
|
132
|
+
`electron-mcp-server` / `tauri-plugin-mcp-server` npm packages (see each
|
|
133
|
+
driver's README). All writes are **TOFU/non-clobbering**: a pre-existing
|
|
134
|
+
server id, config table, or skill file is never overwritten. Opt out with
|
|
135
|
+
`verification.provision_mcp: false` (default `true`) — then init/update
|
|
136
|
+
writes no host MCP config and no skill.
|
|
137
|
+
|
|
114
138
|
### Driver authors
|
|
115
139
|
|
|
116
140
|
Third-party driver authors should run `apt-tools driver doctor .` before
|
|
@@ -32,6 +32,7 @@ import { deriveSidecar } from '../bootstrap/product-context-sidecar.mjs'
|
|
|
32
32
|
import { assertConfigWriteAllowed } from '../config/guard.mjs'
|
|
33
33
|
import {
|
|
34
34
|
AUDIT_001_LOCAL_FIELDS,
|
|
35
|
+
loadMergedProjectConfig,
|
|
35
36
|
migrateDeferredFieldsToLocalConfig,
|
|
36
37
|
writeLocalConfigField,
|
|
37
38
|
} from '../config/load.mjs'
|
|
@@ -42,6 +43,7 @@ import { detectPublicRemote } from '../git/remote.mjs'
|
|
|
42
43
|
import { detectArchetype } from '../install/archetype.mjs'
|
|
43
44
|
import { installKernel } from '../install/install-kernel.mjs'
|
|
44
45
|
import { MANIFEST_FILENAME } from '../install/manifest.mjs'
|
|
46
|
+
import { provisionMcpConfig } from '../install/mcp-provision.mjs'
|
|
45
47
|
import {
|
|
46
48
|
discoverCanonicalFiles,
|
|
47
49
|
runInstall,
|
|
@@ -59,7 +61,7 @@ import { detectAgentRuntime } from '../util/agent-runtime.mjs'
|
|
|
59
61
|
import { buildAptSection } from '../util/aperant-section.mjs'
|
|
60
62
|
import { atomicWriteJson } from '../util/atomic-write.mjs'
|
|
61
63
|
import { aperantBanner } from '../util/banner.mjs'
|
|
62
|
-
import { copyDirRecursive, injectInstructionFile } from '../util/copy.mjs'
|
|
64
|
+
import { checkInstructionFile, copyDirRecursive, injectInstructionFile } from '../util/copy.mjs'
|
|
63
65
|
import { withFileLock } from '../util/fs-lock.mjs'
|
|
64
66
|
import { emitInitConfigHint } from '../util/init-hints.mjs'
|
|
65
67
|
import { err, exitWith, ok, rawText } from '../util/result.mjs'
|
|
@@ -1304,6 +1306,23 @@ export async function cmdInit(projectDir, extraArgs = []) {
|
|
|
1304
1306
|
if (envelope?.__earlyExit) {
|
|
1305
1307
|
return envelope.__earlyExit
|
|
1306
1308
|
}
|
|
1309
|
+
|
|
1310
|
+
// Auto-provision host MCP-server config + the agent-browser skill for
|
|
1311
|
+
// the detected runtime (C38). Runs on BOTH the fresh install AND the
|
|
1312
|
+
// no-drift /apt:update upgrade path (both reach finishInit). finishInit
|
|
1313
|
+
// has already written .aperant/config.json from the template, so the
|
|
1314
|
+
// merged config carries verification.provision_mcp (default true) — the
|
|
1315
|
+
// opt-out gate. TOFU/non-clobbering; never throws.
|
|
1316
|
+
const mergedConfig = loadMergedProjectConfig(installTarget)
|
|
1317
|
+
const mcpProvision = provisionMcpConfig({
|
|
1318
|
+
targetDir: installTarget,
|
|
1319
|
+
selectedRuntimes: effectiveAnswers.runtimes,
|
|
1320
|
+
config: mergedConfig,
|
|
1321
|
+
frameworkRoot: canonicalRoot,
|
|
1322
|
+
version,
|
|
1323
|
+
})
|
|
1324
|
+
envelope.mcp_provision = mcpProvision
|
|
1325
|
+
|
|
1307
1326
|
if (interactiveTty) {
|
|
1308
1327
|
await renderOutro(effectiveAnswers, results, version, kernelResult)
|
|
1309
1328
|
// TTY mode: envelope was persisted to disk + suppressed from stdout
|
|
@@ -1459,6 +1478,69 @@ async function renderOutro(answers, results, version, kernelResult) {
|
|
|
1459
1478
|
clack.outro(`Installed ${fileCount} files · v${version} · ${xverifyCopy}`)
|
|
1460
1479
|
}
|
|
1461
1480
|
|
|
1481
|
+
/**
|
|
1482
|
+
* Resolve the deduplicated set of root instruction-file paths the selected
|
|
1483
|
+
* runtimes read. Each chosen runtime contributes its `nativeInstructionFile`
|
|
1484
|
+
* (claude→CLAUDE.md, codex/opencode→AGENTS.md, gemini→GEMINI.md); runtimes
|
|
1485
|
+
* mapped to `null` install to their own surface and contribute nothing.
|
|
1486
|
+
* Filenames are deduplicated (codex + opencode both map to AGENTS.md → one
|
|
1487
|
+
* target).
|
|
1488
|
+
*
|
|
1489
|
+
* Resolution rules:
|
|
1490
|
+
* - Multiple distinct targets (e.g. `--claude --codex`): write all of them
|
|
1491
|
+
* verbatim — that is the whole point of the fix (AC1, AC2, AC5).
|
|
1492
|
+
* - Exactly one resolved target that does NOT exist AND it is CLAUDE.md, but
|
|
1493
|
+
* AGENTS.md already exists (the `--claude` with a pre-seeded AGENTS.md and
|
|
1494
|
+
* no CLAUDE.md case, Risk 4 / AC9): redirect to the existing AGENTS.md
|
|
1495
|
+
* rather than creating an orphan CLAUDE.md. This is safe ONLY for the
|
|
1496
|
+
* claude runtime because native Claude Code reads AGENTS.md as well as
|
|
1497
|
+
* CLAUDE.md. The redirect is NOT applied for gemini (reads only GEMINI.md)
|
|
1498
|
+
* or codex/opencode (read only AGENTS.md) — redirecting those to an
|
|
1499
|
+
* unrelated existing file would reintroduce the very visibility gap this
|
|
1500
|
+
* fix closes, so their absent target is simply created.
|
|
1501
|
+
* - Zero resolved targets (no selected runtime maps to an instruction file):
|
|
1502
|
+
* fall back to the legacy heuristic — CLAUDE.md if present, else AGENTS.md
|
|
1503
|
+
* if present, else a new CLAUDE.md.
|
|
1504
|
+
*
|
|
1505
|
+
* @param {string[]} chosenIds
|
|
1506
|
+
* @param {string} targetDir
|
|
1507
|
+
* @returns {string[]} absolute instruction-file paths, deduplicated
|
|
1508
|
+
*/
|
|
1509
|
+
function resolveInstructionTargets(chosenIds, targetDir) {
|
|
1510
|
+
const filenames = []
|
|
1511
|
+
for (const id of chosenIds) {
|
|
1512
|
+
const runtime = getRuntime(id)
|
|
1513
|
+
const file = runtime?.nativeInstructionFile
|
|
1514
|
+
if (file && !filenames.includes(file)) filenames.push(file)
|
|
1515
|
+
}
|
|
1516
|
+
const claudeMdPath = join(targetDir, 'CLAUDE.md')
|
|
1517
|
+
const agentsMdPath = join(targetDir, 'AGENTS.md')
|
|
1518
|
+
const resolved = filenames.map((name) => join(targetDir, name))
|
|
1519
|
+
|
|
1520
|
+
if (resolved.length === 0) {
|
|
1521
|
+
// No selected runtime maps to an instruction file — legacy heuristic.
|
|
1522
|
+
if (existsSync(claudeMdPath)) return [claudeMdPath]
|
|
1523
|
+
if (existsSync(agentsMdPath)) return [agentsMdPath]
|
|
1524
|
+
return [claudeMdPath]
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
if (resolved.length === 1 && !existsSync(resolved[0])) {
|
|
1528
|
+
// Single registry target that doesn't exist yet. The ONLY safe redirect
|
|
1529
|
+
// is claude→AGENTS.md (Risk 4 / AC9): native Claude Code reads AGENTS.md
|
|
1530
|
+
// too, so when CLAUDE.md is absent but AGENTS.md exists (e.g. a prior
|
|
1531
|
+
// codex install), writing AGENTS.md still serves claude and avoids an
|
|
1532
|
+
// orphan CLAUDE.md. gemini/codex/opencode read only their own native
|
|
1533
|
+
// file — redirecting them to an unrelated existing file would hide the
|
|
1534
|
+
// catalog from their CLI, so we just create their target.
|
|
1535
|
+
if (resolved[0] === claudeMdPath && existsSync(agentsMdPath)) return [agentsMdPath]
|
|
1536
|
+
return resolved
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
// One or more registry targets, at least one exists OR multiple distinct
|
|
1540
|
+
// targets — write the registry-resolved set verbatim.
|
|
1541
|
+
return resolved
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1462
1544
|
/**
|
|
1463
1545
|
* Synchronous tail of init — runs after the runtime-install promise
|
|
1464
1546
|
* resolves. Performs all .aperant/ + Claude-hook + remote-warning work
|
|
@@ -1652,15 +1734,19 @@ function finishInit(
|
|
|
1652
1734
|
copyDirRecursive(installedContextSrc, join(aperantDir, 'context', 'installed'))
|
|
1653
1735
|
}
|
|
1654
1736
|
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1737
|
+
// Resolve every distinct instruction file the selected runtimes read, then
|
|
1738
|
+
// preflight ALL of them before writing ANY — so a single foreign-row abort
|
|
1739
|
+
// leaves no file partially written (US-03 / AC3). Each chosen runtime maps
|
|
1740
|
+
// to its `nativeInstructionFile` (claude→CLAUDE.md, codex/opencode→AGENTS.md,
|
|
1741
|
+
// gemini→GEMINI.md); null-mapped runtimes install to their own surface and
|
|
1742
|
+
// contribute no target. When the selected runtimes resolve to zero targets,
|
|
1743
|
+
// or none of the resolved targets exist on disk, fall back to the legacy
|
|
1744
|
+
// "pick the existing file" heuristic so e.g. `--claude` with a pre-seeded
|
|
1745
|
+
// AGENTS.md (and no CLAUDE.md) still writes AGENTS.md (Risk 4 / AC9).
|
|
1746
|
+
const instructionTargets = resolveInstructionTargets(chosenIds, targetDir)
|
|
1661
1747
|
// FRAMEWORK-BUG-023 — generate the section from discoverSkills before
|
|
1662
1748
|
// handing it to the writer. The writer also runs a foreign-row defensive
|
|
1663
|
-
// check; if rows present in
|
|
1749
|
+
// check; if rows present in any file aren't in the generated set we
|
|
1664
1750
|
// signal exit-2 via the __earlyExit sentinel so cmdInit can route the
|
|
1665
1751
|
// envelope through exitWith() without writing a malformed install.
|
|
1666
1752
|
const aptSection = buildAptSection({
|
|
@@ -1668,11 +1754,59 @@ function finishInit(
|
|
|
1668
1754
|
targetDir,
|
|
1669
1755
|
cli: detectAgentRuntime(),
|
|
1670
1756
|
})
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1757
|
+
// Preflight pass — check every target with the write-free helper; collect
|
|
1758
|
+
// all aborting targets so the error lists every offender, not just the first.
|
|
1759
|
+
const abortingTargets = []
|
|
1760
|
+
for (const filePath of instructionTargets) {
|
|
1761
|
+
const check = checkInstructionFile(filePath, { aptSection, force: forceOverwriteCatalog })
|
|
1762
|
+
if (check.status === 'aborted-foreign-rows') {
|
|
1763
|
+
abortingTargets.push({ path: basename(filePath), foreignRowIds: check.foreignRowIds })
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
if (abortingTargets.length > 0) {
|
|
1767
|
+
const allForeignRowIds = [...new Set(abortingTargets.flatMap((t) => t.foreignRowIds))]
|
|
1768
|
+
const fileList = abortingTargets.map((t) => t.path).join(', ')
|
|
1769
|
+
return {
|
|
1770
|
+
__earlyExit: exitWith(
|
|
1771
|
+
{
|
|
1772
|
+
ok: false,
|
|
1773
|
+
status: 'error',
|
|
1774
|
+
command: 'init',
|
|
1775
|
+
code: 'instruction-file.foreign-rows',
|
|
1776
|
+
message:
|
|
1777
|
+
`${fileList} contains skill rows not present in the installed framework: ` +
|
|
1778
|
+
`${allForeignRowIds.join(', ')}. ` +
|
|
1779
|
+
'Re-run with --force-overwrite-catalog to regenerate anyway, or remove the foreign rows manually.',
|
|
1780
|
+
foreignRowIds: allForeignRowIds,
|
|
1781
|
+
instructionFile: { path: abortingTargets[0].path, status: 'aborted-foreign-rows' },
|
|
1782
|
+
instructionFiles: abortingTargets.map((t) => ({
|
|
1783
|
+
path: t.path,
|
|
1784
|
+
status: 'aborted-foreign-rows',
|
|
1785
|
+
})),
|
|
1786
|
+
},
|
|
1787
|
+
2,
|
|
1788
|
+
),
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
// Write pass — no target aborted in the preflight, so write all.
|
|
1792
|
+
const instructionFiles = instructionTargets.map((filePath) => {
|
|
1793
|
+
const outcome = injectInstructionFile(filePath, {
|
|
1794
|
+
aptSection,
|
|
1795
|
+
force: forceOverwriteCatalog,
|
|
1796
|
+
})
|
|
1797
|
+
return {
|
|
1798
|
+
path: basename(filePath),
|
|
1799
|
+
status: outcome.status,
|
|
1800
|
+
foreignRowIds: outcome.foreignRowIds,
|
|
1801
|
+
}
|
|
1674
1802
|
})
|
|
1675
|
-
if
|
|
1803
|
+
// TOCTOU guard: if a file gained foreign rows BETWEEN the preflight check and
|
|
1804
|
+
// the write (e.g. another process wrote to it), surface the same error envelope
|
|
1805
|
+
// the preflight uses rather than silently accepting partial writes.
|
|
1806
|
+
const writtenAborts = instructionFiles.filter((f) => f.status === 'aborted-foreign-rows')
|
|
1807
|
+
if (writtenAborts.length > 0) {
|
|
1808
|
+
const allForeignRowIds = [...new Set(writtenAborts.flatMap((f) => f.foreignRowIds ?? []))]
|
|
1809
|
+
const fileList = writtenAborts.map((f) => f.path).join(', ')
|
|
1676
1810
|
return {
|
|
1677
1811
|
__earlyExit: exitWith(
|
|
1678
1812
|
{
|
|
@@ -1681,17 +1815,22 @@ function finishInit(
|
|
|
1681
1815
|
command: 'init',
|
|
1682
1816
|
code: 'instruction-file.foreign-rows',
|
|
1683
1817
|
message:
|
|
1684
|
-
`${
|
|
1685
|
-
`${
|
|
1818
|
+
`${fileList} contains skill rows not present in the installed framework: ` +
|
|
1819
|
+
`${allForeignRowIds.join(', ')}. ` +
|
|
1686
1820
|
'Re-run with --force-overwrite-catalog to regenerate anyway, or remove the foreign rows manually.',
|
|
1687
|
-
foreignRowIds:
|
|
1688
|
-
instructionFile:
|
|
1821
|
+
foreignRowIds: allForeignRowIds,
|
|
1822
|
+
instructionFile: { path: writtenAborts[0].path, status: 'aborted-foreign-rows' },
|
|
1823
|
+
instructionFiles: writtenAborts.map((f) => ({
|
|
1824
|
+
path: f.path,
|
|
1825
|
+
status: 'aborted-foreign-rows',
|
|
1826
|
+
})),
|
|
1689
1827
|
},
|
|
1690
1828
|
2,
|
|
1691
1829
|
),
|
|
1692
1830
|
}
|
|
1693
1831
|
}
|
|
1694
|
-
|
|
1832
|
+
// Primary entry drives the backwards-compat singular alias + structure key.
|
|
1833
|
+
const primaryInstructionFile = instructionFiles[0]
|
|
1695
1834
|
|
|
1696
1835
|
const installedClaude = chosenIds.includes('claude') || existsSync(join(targetDir, '.claude'))
|
|
1697
1836
|
const precompactResult = installedClaude
|
|
@@ -1730,9 +1869,11 @@ function finishInit(
|
|
|
1730
1869
|
'.aperant/events/': 'event log (git-ignored)',
|
|
1731
1870
|
'.aperant/prompts/': 'agent prompt templates',
|
|
1732
1871
|
'.aperant/install/last-install.json': 'cross-runtime install summary',
|
|
1733
|
-
[basename(instructionFile)]: `APT section ${injectionResult}`,
|
|
1734
1872
|
'.claude/settings.local.json': `PreCompact hook ${precompactResult}; SessionStart hook ${sessionStartResult}`,
|
|
1735
1873
|
}
|
|
1874
|
+
for (const f of instructionFiles) {
|
|
1875
|
+
structure[f.path] = `APT section ${f.status}`
|
|
1876
|
+
}
|
|
1736
1877
|
if (kernelResult) {
|
|
1737
1878
|
structure['.aperant/deps/'] =
|
|
1738
1879
|
kernelResult.status === 'installed'
|
|
@@ -1772,7 +1913,10 @@ function finishInit(
|
|
|
1772
1913
|
framework_version: version,
|
|
1773
1914
|
configCreated,
|
|
1774
1915
|
configMigrated,
|
|
1775
|
-
|
|
1916
|
+
// Plural contract (ID-03): one entry per instruction file written.
|
|
1917
|
+
// `instructionFile` (singular) is a backwards-compat alias = first entry.
|
|
1918
|
+
instructionFiles,
|
|
1919
|
+
instructionFile: primaryInstructionFile,
|
|
1776
1920
|
stateCreated,
|
|
1777
1921
|
seedsCreated,
|
|
1778
1922
|
publicRemoteWarning,
|
|
@@ -274,6 +274,32 @@ export function cmdToolchainDetect(projectDir, extraArgs = []) {
|
|
|
274
274
|
}
|
|
275
275
|
const scripts = packageJson?.scripts ?? {}
|
|
276
276
|
|
|
277
|
+
// FRAMEWORK-BUG-006 (Part B) — turbo cache-bypass detection.
|
|
278
|
+
// A turbo-backed verify command can replay a STALE CACHE HIT and report
|
|
279
|
+
// PASS while a broken import sits on disk (sighted on PR #244). When the
|
|
280
|
+
// project is turbo-backed we additionally emit a `no_cache_command`
|
|
281
|
+
// variant that appends turbo's `--force` flag so pr-review (and any
|
|
282
|
+
// consumer) can run a cache-bypassing verification. This is ADDITIVE —
|
|
283
|
+
// the existing `command` strings are never altered (ID-03).
|
|
284
|
+
const hasTurboConfig = existsSync(join(scopePath, 'turbo.json'))
|
|
285
|
+
// A package script "resolves through turbo" when its script body invokes
|
|
286
|
+
// the turbo CLI (e.g. "turbo run typecheck"). package.json scripts are
|
|
287
|
+
// hand-authored and tiny; a substring match on the `turbo` token is
|
|
288
|
+
// sufficient and avoids parsing shell.
|
|
289
|
+
function scriptIsTurboBacked(scriptName) {
|
|
290
|
+
const body = scripts[scriptName]
|
|
291
|
+
return typeof body === 'string' && /\bturbo\b/.test(body)
|
|
292
|
+
}
|
|
293
|
+
// Append --force to a turbo-backed package script so it reaches the
|
|
294
|
+
// underlying `turbo run …`. Only used when the command is known to be
|
|
295
|
+
// turbo-backed, so --force is always meaningful here. npm does NOT
|
|
296
|
+
// forward trailing args to the script unless they follow a `--`
|
|
297
|
+
// separator (`npm run typecheck --force` swallows --force as an npm flag),
|
|
298
|
+
// so insert `--` for npm. pnpm/yarn/bun forward trailing args directly.
|
|
299
|
+
function withTurboForce(command) {
|
|
300
|
+
return pm === 'npm' ? `${command} -- --force` : `${command} --force`
|
|
301
|
+
}
|
|
302
|
+
|
|
277
303
|
// Read pyproject.toml as string if present
|
|
278
304
|
let pyproject = null
|
|
279
305
|
const pyprojectPath = join(scopePath, 'pyproject.toml')
|
|
@@ -331,6 +357,19 @@ export function cmdToolchainDetect(projectDir, extraArgs = []) {
|
|
|
331
357
|
} else if (pyproject && pyproject.includes('[tool.mypy]')) {
|
|
332
358
|
typecheck = { tool: 'mypy', command: 'mypy .' }
|
|
333
359
|
}
|
|
360
|
+
// Additive turbo cache-bypass variant (FRAMEWORK-BUG-006). When the
|
|
361
|
+
// project is turbo-backed AND has a turbo-backed typecheck script, the
|
|
362
|
+
// plain command (often `tsc --noEmit` when tsconfig.json wins above) can
|
|
363
|
+
// be masked by a replayed turbo cache PASS. The no_cache_command routes
|
|
364
|
+
// through the turbo-backed script with `--force` so pr-review's post-fix
|
|
365
|
+
// typecheck cannot be fooled by a stale cache.
|
|
366
|
+
if (typecheck && hasTurboConfig) {
|
|
367
|
+
if (scriptIsTurboBacked('typecheck')) {
|
|
368
|
+
typecheck.no_cache_command = withTurboForce(`${pmRun} typecheck`)
|
|
369
|
+
} else if (scriptIsTurboBacked('type-check')) {
|
|
370
|
+
typecheck.no_cache_command = withTurboForce(`${pmRun} type-check`)
|
|
371
|
+
}
|
|
372
|
+
}
|
|
334
373
|
|
|
335
374
|
// --- Test ---
|
|
336
375
|
let test = null
|
|
@@ -364,6 +403,11 @@ export function cmdToolchainDetect(projectDir, extraArgs = []) {
|
|
|
364
403
|
if (!test && scripts.test) {
|
|
365
404
|
test = { tool: 'package-script', command: `${pmRun} test` }
|
|
366
405
|
}
|
|
406
|
+
// Additive turbo cache-bypass variant for tests (FRAMEWORK-BUG-006), same
|
|
407
|
+
// rationale as typecheck above — only when turbo backs the test script.
|
|
408
|
+
if (test && hasTurboConfig && scriptIsTurboBacked('test')) {
|
|
409
|
+
test.no_cache_command = withTurboForce(`${pmRun} test`)
|
|
410
|
+
}
|
|
367
411
|
|
|
368
412
|
// --- Build ---
|
|
369
413
|
let build = null
|