@aperant/framework 0.15.0 → 0.17.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 +52 -1
- package/agents/{apt-pr-review-xplatform.md → apt-pr-review-crossplatform.md} +1 -1
- package/dist/cli/commands/init.d.mts.map +1 -1
- package/dist/cli/commands/init.mjs +30 -1
- package/dist/cli/commands/init.mjs.map +1 -1
- package/dist/cli/commands/pr-review/detect-specialists.d.mts +5 -5
- package/dist/cli/commands/pr-review/detect-specialists.d.mts.map +1 -1
- package/dist/cli/commands/pr-review/detect-specialists.mjs +49 -45
- package/dist/cli/commands/pr-review/detect-specialists.mjs.map +1 -1
- package/dist/cli/commands/spar-resolve-partner.d.mts +9 -0
- package/dist/cli/commands/spar-resolve-partner.d.mts.map +1 -0
- package/dist/cli/commands/spar-resolve-partner.mjs +82 -0
- package/dist/cli/commands/spar-resolve-partner.mjs.map +1 -0
- package/dist/cli/config/gitignore-drift.d.mts.map +1 -1
- package/dist/cli/config/gitignore-drift.mjs +1 -0
- package/dist/cli/config/gitignore-drift.mjs.map +1 -1
- package/dist/cli/config/post-merge-sweep.d.mts.map +1 -1
- package/dist/cli/config/post-merge-sweep.mjs +1 -0
- package/dist/cli/config/post-merge-sweep.mjs.map +1 -1
- package/dist/cli/dispatch.d.mts.map +1 -1
- package/dist/cli/dispatch.mjs +2 -0
- package/dist/cli/dispatch.mjs.map +1 -1
- package/dist/cli/help.d.mts.map +1 -1
- package/dist/cli/help.mjs +1 -0
- package/dist/cli/help.mjs.map +1 -1
- package/dist/cli/host/codex-config.d.mts +78 -0
- package/dist/cli/host/codex-config.d.mts.map +1 -0
- package/dist/cli/host/codex-config.mjs +169 -0
- package/dist/cli/host/codex-config.mjs.map +1 -0
- package/dist/cli/install/legacy-paths.d.mts.map +1 -1
- package/dist/cli/install/legacy-paths.mjs +1 -0
- package/dist/cli/install/legacy-paths.mjs.map +1 -1
- package/dist/cli/wfrun/run.d.mts.map +1 -1
- package/dist/cli/wfrun/run.mjs +1 -0
- package/dist/cli/wfrun/run.mjs.map +1 -1
- package/dist/plugin/.claude-plugin/plugin.json +2 -2
- package/dist/plugin/agents/{apt-pr-review-xplatform.md → apt-pr-review-crossplatform.md} +1 -1
- package/dist/plugin/skills/apt-pr-review/SKILL.md +13 -13
- package/dist/plugin/skills/apt-spar/SKILL.md +14 -6
- package/package.json +1 -1
- package/skills/apt-pr-review/SKILL.md +13 -13
- package/skills/apt-spar/SKILL.md +14 -6
- package/src/cli/commands/init.mjs +29 -0
- package/src/cli/commands/pr-review/detect-specialists.mjs +51 -45
- package/src/cli/commands/spar-resolve-partner.mjs +85 -0
- package/src/cli/config/gitignore-drift.mjs +1 -0
- package/src/cli/config/post-merge-sweep.mjs +1 -0
- package/src/cli/dispatch.mjs +2 -0
- package/src/cli/help.mjs +1 -0
- package/src/cli/host/codex-config.mjs +165 -0
- package/src/cli/install/legacy-paths.mjs +1 -0
- package/src/cli/wfrun/run.mjs +1 -0
- /package/prompts/github/{pr_xplatform_agent.md → pr_crossplatform_agent.md} +0 -0
package/src/cli/dispatch.mjs
CHANGED
|
@@ -86,6 +86,7 @@ import { cmdRiskCheck } from './commands/risk-check.mjs'
|
|
|
86
86
|
import { cmdRoadmap } from './commands/roadmap.mjs'
|
|
87
87
|
import { cmdRoute } from './commands/route.mjs'
|
|
88
88
|
import { cmdSeed } from './commands/seed.mjs'
|
|
89
|
+
import { cmdSparResolvePartner } from './commands/spar-resolve-partner.mjs'
|
|
89
90
|
import { cmdStandards } from './commands/standards.mjs'
|
|
90
91
|
import { cmdConfig, cmdState, cmdStateArtifacts, cmdStateUpdate } from './commands/state.mjs'
|
|
91
92
|
import { cmdStatusline } from './commands/statusline.mjs'
|
|
@@ -285,6 +286,7 @@ export const DISPATCH = {
|
|
|
285
286
|
'author-skill': (args) => wrap(cmdAuthorSkill)(args[1], args.slice(2)),
|
|
286
287
|
'toolchain-detect': (args) => wrap(cmdToolchainDetect)(args[1], args.slice(2)),
|
|
287
288
|
'host-detect': () => wrap(cmdHostDetect)(),
|
|
289
|
+
'spar-resolve-partner': (args) => wrap(cmdSparResolvePartner)(args.slice(1)),
|
|
288
290
|
reconcile: (args) => wrap(cmdReconcile)(args[1], args.slice(2)),
|
|
289
291
|
standards: (args) => wrap(cmdStandards)(args[1], args.slice(2)),
|
|
290
292
|
'precompact-hook': (args) => wrap(cmdPrecompactHook)(args[1]),
|
package/src/cli/help.mjs
CHANGED
|
@@ -110,6 +110,7 @@ Commands:
|
|
|
110
110
|
toolchain-detect <project-dir> Detect project toolchain (linter, formatter, typecheck, test, build)
|
|
111
111
|
health-check <project-dir> Detect dirty state, stale locks, orphaned events, version drift
|
|
112
112
|
host-detect Detect which AI CLI is running and its capability matrix
|
|
113
|
+
spar-resolve-partner [--partner codex|claude|gemini] apt:spar live partner-model resolution — reads ~/.codex/config.toml LIVE (fail-closed, no baked default); codex returns {available,model,source,reason} + a transport-walk preference, claude/gemini return self_resolving
|
|
113
114
|
reconcile <project-dir> Resolve orchestration preferences against host capabilities
|
|
114
115
|
|
|
115
116
|
migrate-pr-reviews <project-dir> Migrate legacy .aperant/reviews/ → .aperant/pr-reviews/pr-{N}/rounds/{R}/ (idempotent)
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* host/codex-config.mjs — live codex model resolution for `apt:spar` (Decision B + C).
|
|
3
|
+
*
|
|
4
|
+
* Aperant holds ZERO model names. The only source of truth for the codex
|
|
5
|
+
* partner's model is the user's live `~/.codex/config.toml`, read at invocation
|
|
6
|
+
* time with NO fallback default — mirroring the app's "Model selection —
|
|
7
|
+
* resolver-only, provider-is-output, LIVE" rule.
|
|
8
|
+
*
|
|
9
|
+
* Two exports:
|
|
10
|
+
* - `parseTopLevelCodexModel(tomlText)` — a deterministic top-level TOML
|
|
11
|
+
* `model = "<value>"` line parser (ID-04: top-level only; `[profiles.*]`
|
|
12
|
+
* sections are inactive without `--profile`, which spar never passes).
|
|
13
|
+
* - `resolveCodexSparModel({ home, existsSync, readFileSync })` — reads
|
|
14
|
+
* `<home>/.codex/config.toml` LIVE each call and returns a fail-closed
|
|
15
|
+
* `{ available, model, source, reason }` envelope. NEVER a baked constant.
|
|
16
|
+
*
|
|
17
|
+
* Parser hardening (plan-gate spar refinement #1). The bias rule is
|
|
18
|
+
* fail-closed: when a value is ambiguous, return `reason: 'unparseable'` rather
|
|
19
|
+
* than a wrong or truncated model. A too-strict parse that loses the MCP rung
|
|
20
|
+
* (falling through to the self-resolving plugin/CLI rungs) is acceptable; a
|
|
21
|
+
* too-loose parse that passes a wrong model is a bug.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { join } from 'node:path'
|
|
25
|
+
|
|
26
|
+
/** Human-facing source label the SKILL + envelope reference. */
|
|
27
|
+
export const CODEX_CONFIG_SOURCE = '~/.codex/config.toml'
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {Object} ParsedCodexModel
|
|
31
|
+
* @property {string|null} model — the resolved top-level model, or null.
|
|
32
|
+
* @property {null|'no_model_key'|'unparseable'} reason — null when a model
|
|
33
|
+
* resolved; a machine reason otherwise.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Parse the FIRST top-level `model = "<value>"` assignment from codex
|
|
38
|
+
* `config.toml` text. Scans line-by-line, stops at the first `[section]`
|
|
39
|
+
* header (top-level scope ends there), skips comment lines, and resolves the
|
|
40
|
+
* first top-level `model` key.
|
|
41
|
+
*
|
|
42
|
+
* Handles (refinement #1): a leading UTF-8 BOM, an inline `# comment` after the
|
|
43
|
+
* value, a no-space `model="x"` assignment, single-quoted TOML literal strings,
|
|
44
|
+
* and commented-out `#model = "x"` keys (skipped). A malformed / unterminated /
|
|
45
|
+
* escaped-quote value returns `reason: 'unparseable'` — never a truncated
|
|
46
|
+
* string. The first top-level `model` line is decisive: if it is malformed the
|
|
47
|
+
* parser returns `unparseable` rather than scanning for a later valid line
|
|
48
|
+
* (TOML forbids duplicate keys, and a later-line search would be too loose).
|
|
49
|
+
*
|
|
50
|
+
* @param {string} tomlText
|
|
51
|
+
* @returns {ParsedCodexModel}
|
|
52
|
+
*/
|
|
53
|
+
export function parseTopLevelCodexModel(tomlText) {
|
|
54
|
+
if (typeof tomlText !== 'string') return { model: null, reason: 'unparseable' }
|
|
55
|
+
|
|
56
|
+
// Strip a leading UTF-8 BOM so `model = "x"` still resolves.
|
|
57
|
+
let text = tomlText
|
|
58
|
+
if (text.charCodeAt(0) === 0xfeff) text = text.slice(1)
|
|
59
|
+
|
|
60
|
+
const lines = text.split(/\r?\n/)
|
|
61
|
+
for (const rawLine of lines) {
|
|
62
|
+
const line = rawLine.trimStart()
|
|
63
|
+
// A `[section]` (or `[[array.of.tables]]`) header ends the top-level
|
|
64
|
+
// scope — stop scanning; a model below this point is profile-scoped.
|
|
65
|
+
if (line.startsWith('[')) break
|
|
66
|
+
// Comment lines (incl. a commented-out `#model = "x"`) are skipped.
|
|
67
|
+
if (line.startsWith('#')) continue
|
|
68
|
+
// Top-level `model` assignment? The key must be EXACTLY `model` —
|
|
69
|
+
// `\s*=` rejects `model_provider`, `models`, `mcp_model`, etc.
|
|
70
|
+
const assign = line.match(/^model\s*=\s*(.*)$/)
|
|
71
|
+
if (!assign) continue
|
|
72
|
+
// First top-level model line is decisive (fail-closed on malformed).
|
|
73
|
+
return parseQuotedValue(assign[1])
|
|
74
|
+
}
|
|
75
|
+
return { model: null, reason: 'no_model_key' }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Parse a quoted TOML string value (the RHS of `model =`). Accepts a
|
|
80
|
+
* double-quoted basic string or a single-quoted literal string, each optionally
|
|
81
|
+
* followed by whitespace and/or an inline `# comment`. Any other shape
|
|
82
|
+
* (unquoted, unterminated, escaped, empty, or trailing non-comment junk) is
|
|
83
|
+
* fail-closed `unparseable`.
|
|
84
|
+
*
|
|
85
|
+
* @param {string} value — the text after `model\s*=\s*`.
|
|
86
|
+
* @returns {ParsedCodexModel}
|
|
87
|
+
*/
|
|
88
|
+
function parseQuotedValue(value) {
|
|
89
|
+
const first = value[0]
|
|
90
|
+
if (first === '"') {
|
|
91
|
+
const m = value.match(/^"([^"\n]*)"(.*)$/)
|
|
92
|
+
if (!m) return { model: null, reason: 'unparseable' } // unterminated
|
|
93
|
+
const inner = m[1]
|
|
94
|
+
// A backslash means TOML escapes are in play — do NOT attempt to
|
|
95
|
+
// unescape; fail closed rather than risk a wrong/truncated model.
|
|
96
|
+
if (inner.includes('\\')) return { model: null, reason: 'unparseable' }
|
|
97
|
+
if (!isOnlyTrailingComment(m[2])) return { model: null, reason: 'unparseable' }
|
|
98
|
+
if (inner.length === 0) return { model: null, reason: 'unparseable' }
|
|
99
|
+
return { model: inner, reason: null }
|
|
100
|
+
}
|
|
101
|
+
if (first === "'") {
|
|
102
|
+
// TOML literal strings carry no escapes.
|
|
103
|
+
const m = value.match(/^'([^'\n]*)'(.*)$/)
|
|
104
|
+
if (!m) return { model: null, reason: 'unparseable' }
|
|
105
|
+
const inner = m[1]
|
|
106
|
+
if (!isOnlyTrailingComment(m[2])) return { model: null, reason: 'unparseable' }
|
|
107
|
+
if (inner.length === 0) return { model: null, reason: 'unparseable' }
|
|
108
|
+
return { model: inner, reason: null }
|
|
109
|
+
}
|
|
110
|
+
// Unquoted / bare value — codex `model` is always a quoted string; bias
|
|
111
|
+
// fail-closed so a malformed line never yields a wrong model.
|
|
112
|
+
return { model: null, reason: 'unparseable' }
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* The text after a closing quote must be empty, whitespace, or an inline
|
|
117
|
+
* `# comment`. Anything else is malformed → fail closed.
|
|
118
|
+
*
|
|
119
|
+
* @param {string} rest
|
|
120
|
+
* @returns {boolean}
|
|
121
|
+
*/
|
|
122
|
+
function isOnlyTrailingComment(rest) {
|
|
123
|
+
const t = rest.trimStart()
|
|
124
|
+
return t === '' || t.startsWith('#')
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* @typedef {Object} CodexSparModelResolution
|
|
129
|
+
* @property {boolean} available — true iff a live top-level model resolved.
|
|
130
|
+
* @property {string|null} model — the resolved model, or null when unavailable.
|
|
131
|
+
* @property {string} source — the config source label (`~/.codex/config.toml`).
|
|
132
|
+
* @property {null|'config_missing'|'no_model_key'|'unparseable'} reason
|
|
133
|
+
*/
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Read `<home>/.codex/config.toml` LIVE and resolve the top-level model.
|
|
137
|
+
* Fail-closed (Decision C / ID-03): a missing file, a missing top-level `model`
|
|
138
|
+
* key, or an unparseable value returns `available: false` with a machine reason
|
|
139
|
+
* and `model: null`. It NEVER substitutes a default. There is no module-level
|
|
140
|
+
* cached value — the file is re-read on every call.
|
|
141
|
+
*
|
|
142
|
+
* `existsSync` / `readFileSync` are injected so tests stay hermetic (no real
|
|
143
|
+
* `$HOME` read).
|
|
144
|
+
*
|
|
145
|
+
* @param {{ home: string, existsSync: (p: string) => boolean, readFileSync: (p: string, enc: string) => string }} deps
|
|
146
|
+
* @returns {CodexSparModelResolution}
|
|
147
|
+
*/
|
|
148
|
+
export function resolveCodexSparModel({ home, existsSync, readFileSync }) {
|
|
149
|
+
const source = CODEX_CONFIG_SOURCE
|
|
150
|
+
const configPath = join(home, '.codex', 'config.toml')
|
|
151
|
+
if (!existsSync(configPath)) {
|
|
152
|
+
return { available: false, model: null, source, reason: 'config_missing' }
|
|
153
|
+
}
|
|
154
|
+
let text
|
|
155
|
+
try {
|
|
156
|
+
text = readFileSync(configPath, 'utf-8')
|
|
157
|
+
} catch {
|
|
158
|
+
return { available: false, model: null, source, reason: 'config_missing' }
|
|
159
|
+
}
|
|
160
|
+
const { model, reason } = parseTopLevelCodexModel(text)
|
|
161
|
+
if (model) {
|
|
162
|
+
return { available: true, model, source, reason: null }
|
|
163
|
+
}
|
|
164
|
+
return { available: false, model: null, source, reason: reason ?? 'unparseable' }
|
|
165
|
+
}
|
|
@@ -181,6 +181,7 @@ export const LEGACY_INSTALL_PATHS = Object.freeze({
|
|
|
181
181
|
'agents/apt-improver.md',
|
|
182
182
|
'agents/apt-planner.md',
|
|
183
183
|
'agents/apt-pr-review-codebase-fit.md',
|
|
184
|
+
'agents/apt-pr-review-crossplatform.md',
|
|
184
185
|
'agents/apt-pr-review-fixer.md',
|
|
185
186
|
'agents/apt-pr-review-i18n.md',
|
|
186
187
|
'agents/apt-pr-review-logic.md',
|
package/src/cli/wfrun/run.mjs
CHANGED
|
File without changes
|