@aperant/framework 0.7.0 → 0.7.3
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 +180 -0
- package/agents/apt-planner.md +12 -0
- package/agents/apt-pr-review-fixer.md +13 -9
- package/bin/apt-tools.mjs +7 -0
- package/dist/cli/ci-watch/stop-matrix.d.mts.map +1 -1
- package/dist/cli/ci-watch/stop-matrix.mjs +16 -0
- package/dist/cli/ci-watch/stop-matrix.mjs.map +1 -1
- package/dist/cli/commands/ci-watch.d.mts +11 -0
- package/dist/cli/commands/ci-watch.d.mts.map +1 -1
- package/dist/cli/commands/ci-watch.mjs +108 -2
- package/dist/cli/commands/ci-watch.mjs.map +1 -1
- package/dist/cli/commands/features-audit.d.mts +24 -0
- package/dist/cli/commands/features-audit.d.mts.map +1 -1
- package/dist/cli/commands/features-audit.mjs +159 -5
- package/dist/cli/commands/features-audit.mjs.map +1 -1
- package/dist/cli/commands/health-check.d.mts +16 -0
- package/dist/cli/commands/health-check.d.mts.map +1 -1
- package/dist/cli/commands/health-check.mjs +118 -2
- package/dist/cli/commands/health-check.mjs.map +1 -1
- package/dist/cli/commands/init.d.mts +9 -0
- package/dist/cli/commands/init.d.mts.map +1 -1
- package/dist/cli/commands/init.mjs +49 -4
- package/dist/cli/commands/init.mjs.map +1 -1
- package/dist/cli/commands/modes.d.mts.map +1 -1
- package/dist/cli/commands/modes.mjs +11 -0
- package/dist/cli/commands/modes.mjs.map +1 -1
- package/dist/cli/commands/pr-review-audit-fixer.d.mts +13 -0
- package/dist/cli/commands/pr-review-audit-fixer.d.mts.map +1 -1
- package/dist/cli/commands/pr-review-audit-fixer.mjs +18 -5
- package/dist/cli/commands/pr-review-audit-fixer.mjs.map +1 -1
- package/dist/cli/commands/task.d.mts.map +1 -1
- package/dist/cli/commands/task.mjs +84 -5
- package/dist/cli/commands/task.mjs.map +1 -1
- package/dist/cli/commands/validate-evidence.d.mts +24 -2
- package/dist/cli/commands/validate-evidence.d.mts.map +1 -1
- package/dist/cli/commands/validate-evidence.mjs +154 -17
- package/dist/cli/commands/validate-evidence.mjs.map +1 -1
- package/dist/cli/commands/vitest-doctor.d.mts +2 -0
- package/dist/cli/commands/vitest-doctor.d.mts.map +1 -0
- package/dist/cli/commands/vitest-doctor.mjs +168 -0
- package/dist/cli/commands/vitest-doctor.mjs.map +1 -0
- package/dist/cli/config/gitignore-drift.d.mts +23 -1
- package/dist/cli/config/gitignore-drift.d.mts.map +1 -1
- package/dist/cli/config/gitignore-drift.mjs +81 -3
- package/dist/cli/config/gitignore-drift.mjs.map +1 -1
- package/dist/cli/config/load.d.mts +56 -2
- package/dist/cli/config/load.d.mts.map +1 -1
- package/dist/cli/config/load.mjs +192 -2
- package/dist/cli/config/load.mjs.map +1 -1
- package/dist/cli/consistency/parse-review.mjs +7 -0
- package/dist/cli/consistency/parse-review.mjs.map +1 -1
- package/dist/cli/dispatch.d.mts.map +1 -1
- package/dist/cli/dispatch.mjs +24 -2
- package/dist/cli/dispatch.mjs.map +1 -1
- package/dist/cli/gate/gates/gitignore-in-sync.d.mts +1 -1
- package/dist/cli/gate/gates/gitignore-in-sync.d.mts.map +1 -1
- package/dist/cli/gate/gates/gitignore-in-sync.mjs +5 -2
- package/dist/cli/gate/gates/gitignore-in-sync.mjs.map +1 -1
- package/dist/cli/gate/gates/review-clean.d.mts +5 -1
- package/dist/cli/gate/gates/review-clean.d.mts.map +1 -1
- package/dist/cli/gate/gates/review-clean.mjs +23 -18
- package/dist/cli/gate/gates/review-clean.mjs.map +1 -1
- package/dist/cli/gate/gates/verify-approved.d.mts +49 -1
- package/dist/cli/gate/gates/verify-approved.d.mts.map +1 -1
- package/dist/cli/gate/gates/verify-approved.mjs +93 -14
- package/dist/cli/gate/gates/verify-approved.mjs.map +1 -1
- package/dist/cli/help.d.mts.map +1 -1
- package/dist/cli/help.mjs +7 -2
- package/dist/cli/help.mjs.map +1 -1
- package/dist/cli/install/runtime-detect.d.mts +13 -0
- package/dist/cli/install/runtime-detect.d.mts.map +1 -1
- package/dist/cli/install/runtime-detect.mjs +9 -0
- package/dist/cli/install/runtime-detect.mjs.map +1 -1
- package/dist/cli/task/index-md.d.mts.map +1 -1
- package/dist/cli/task/index-md.mjs +14 -2
- package/dist/cli/task/index-md.mjs.map +1 -1
- package/dist/plugin/.claude-plugin/plugin.json +11 -2
- package/dist/plugin/agents/apt-improver.md +99 -0
- package/dist/plugin/agents/apt-planner.md +127 -10
- package/dist/plugin/agents/apt-pr-review-fixer.md +13 -9
- package/dist/plugin/skills/apt/SKILL.md +1 -0
- package/dist/plugin/skills/apt-close-task/SKILL.md +63 -1
- package/dist/plugin/skills/apt-debug/SKILL.md +39 -6
- package/dist/plugin/skills/apt-debug/appendices/diagnose-discipline.md +119 -0
- package/dist/plugin/skills/apt-diagram/SKILL.md +378 -0
- package/dist/plugin/skills/apt-diagram/appendices/design-discipline.md +97 -0
- package/dist/plugin/skills/apt-discuss/SKILL.md +72 -5
- package/dist/plugin/skills/apt-discuss/appendices/grill-discipline.md +104 -0
- package/dist/plugin/skills/apt-discuss/appendices/zoom-out-helper.md +79 -0
- package/dist/plugin/skills/apt-execute/SKILL.md +57 -5
- package/dist/plugin/skills/apt-execute/appendices/tdd-mode.md +107 -0
- package/dist/plugin/skills/apt-improve/DEEPENING.md +84 -0
- package/dist/plugin/skills/apt-improve/INTERFACE-DESIGN.md +97 -0
- package/dist/plugin/skills/apt-improve/LANGUAGE.md +104 -0
- package/dist/plugin/skills/apt-improve/SKILL.md +141 -0
- package/dist/plugin/skills/apt-plan/SKILL.md +171 -4
- package/dist/plugin/skills/apt-plan/adapters/conductor.md +98 -0
- package/dist/plugin/skills/apt-pr-review/SKILL.md +57 -18
- package/dist/plugin/skills/apt-prototype/LOGIC.md +109 -0
- package/dist/plugin/skills/apt-prototype/SKILL.md +143 -0
- package/dist/plugin/skills/apt-prototype/UI.md +90 -0
- package/dist/plugin/skills/apt-quick/SKILL.md +49 -8
- package/dist/plugin/skills/apt-release-notes/SKILL.md +193 -0
- package/dist/plugin/skills/apt-release-notes/appendices/persona-voice.md +59 -0
- package/dist/plugin/skills/apt-review/SKILL.md +2 -0
- package/dist/plugin/skills/apt-run/SKILL.md +32 -4
- package/dist/plugin/skills/apt-setup/SKILL.md +308 -6
- package/dist/plugin/skills/apt-ship/SKILL.md +122 -1
- package/dist/plugin/skills/apt-spar/SKILL.md +315 -0
- package/dist/plugin/skills/apt-triage/AGENT-BRIEF.md +84 -0
- package/dist/plugin/skills/apt-triage/OUT-OF-SCOPE.md +75 -0
- package/dist/plugin/skills/apt-triage/SKILL.md +169 -0
- package/dist/plugin/skills/apt-update/SKILL.md +77 -10
- package/dist/plugin/skills/apt-verify/SKILL.md +3 -0
- package/dist/plugin/skills/apt-verify-proof/SKILL.md +10 -5
- package/dist/plugin/skills/apt-watch-ci/SKILL.md +166 -0
- package/dist/plugin/skills/apt-zoom-out/SKILL.md +130 -0
- package/package.json +133 -133
- package/prompts/conductor-framework-context.md +63 -0
- package/prompts/conductor-system.md +11 -0
- package/skills/apt-close-task/SKILL.md +1 -0
- package/skills/apt-discuss/SKILL.md +47 -5
- package/skills/apt-execute/SKILL.md +9 -0
- package/skills/apt-plan/SKILL.md +12 -0
- package/skills/apt-pr-review/SKILL.md +11 -2
- package/skills/apt-quick/SKILL.md +19 -8
- package/skills/apt-researcher.md +1 -0
- package/skills/apt-setup/SKILL.md +33 -2
- package/skills/apt-verify-proof/SKILL.md +7 -5
- package/src/cli/ci-watch/stop-matrix.mjs +17 -0
- package/src/cli/commands/ci-watch.mjs +113 -2
- package/src/cli/commands/features-audit.mjs +164 -5
- package/src/cli/commands/health-check.mjs +115 -2
- package/src/cli/commands/init.mjs +52 -4
- package/src/cli/commands/modes.mjs +11 -0
- package/src/cli/commands/pr-review-audit-fixer.mjs +18 -5
- package/src/cli/commands/task.mjs +83 -5
- package/src/cli/commands/validate-evidence.mjs +158 -17
- package/src/cli/commands/vitest-doctor.mjs +173 -0
- package/src/cli/config/gitignore-drift.mjs +74 -3
- package/src/cli/config/load.mjs +188 -2
- package/src/cli/consistency/parse-review.mjs +6 -0
- package/src/cli/dispatch.mjs +23 -2
- package/src/cli/gate/gates/gitignore-in-sync.mjs +5 -2
- package/src/cli/gate/gates/review-clean.mjs +24 -19
- package/src/cli/gate/gates/verify-approved.mjs +97 -14
- package/src/cli/help.mjs +7 -2
- package/src/cli/install/runtime-detect.mjs +9 -0
- package/src/cli/task/index-md.mjs +15 -2
- package/templates/config.json +2 -7
- package/workflows/verify-proof.md +8 -3
|
@@ -11,22 +11,167 @@
|
|
|
11
11
|
* 1. `--diff-files <a,b,c>` explicit CSV (highest priority)
|
|
12
12
|
* 2. `--since <ref>` → runs `git diff --name-only <ref>..HEAD`
|
|
13
13
|
* 3. fallback: `git diff HEAD --name-only` (current WIP)
|
|
14
|
+
*
|
|
15
|
+
* BUG-025b worktree-redirect: features-audit historically wrote to the
|
|
16
|
+
* invoking cwd's `.aperant/features/`, leaking generated `*.json` into
|
|
17
|
+
* main when the executor invoked it from a worktree-isolated task. The
|
|
18
|
+
* fix applies a 4-step resolution order (see resolveWriteRoot below):
|
|
19
|
+
* (1) cwd IS a worktree root → write there (current behaviour)
|
|
20
|
+
* (2) --task-id maps to an active non-terminal worktree task →
|
|
21
|
+
* redirect writes to that worktree's `.aperant/features/`
|
|
22
|
+
* (3) multiple active worktree-isolated tasks + no --task-id → error
|
|
23
|
+
* (4) `--no-worktree-redirect` opt-out preserves layer (1) behaviour
|
|
14
24
|
*/
|
|
15
|
-
import { execFileSync } from 'node:child_process';
|
|
16
|
-
import {
|
|
25
|
+
import { execFileSync, spawnSync } from 'node:child_process';
|
|
26
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
27
|
+
import { join, resolve, sep } from 'node:path';
|
|
17
28
|
import { loadDocsPolicy, matchSurfaceToPolicy } from '../docs-audit/policy.mjs';
|
|
18
29
|
import { applyStubPatches, auditFeatureRegistry } from '../features/registry-audit.mjs';
|
|
19
30
|
import { parseFlags } from '../util/args.mjs';
|
|
20
31
|
import { err, ok } from '../util/result.mjs';
|
|
32
|
+
const TERMINAL_PHASES = new Set(['completed', 'cancelled', 'closed', 'shipped', 'merged']);
|
|
33
|
+
/**
|
|
34
|
+
* BUG-025b — resolve where features-audit should write `*.json`.
|
|
35
|
+
* See module header for the 4-step order. Returns `{ writeRoot, redirect_notice }`
|
|
36
|
+
* on success or a synthetic error envelope (`{ error: { ... } }`) on the
|
|
37
|
+
* ambiguous-worktree case.
|
|
38
|
+
*
|
|
39
|
+
* @internal — exported for unit testing.
|
|
40
|
+
*
|
|
41
|
+
* @param {{projectDir: string, taskId?: string | null, noRedirect?: boolean}} args
|
|
42
|
+
* @returns {{writeRoot?: string, redirect_notice?: string | null, error?: {kind: string, message: string, active_worktree_tasks: string[]}}}
|
|
43
|
+
*/
|
|
44
|
+
export function resolveWriteRoot({ projectDir, taskId, noRedirect }) {
|
|
45
|
+
const root = resolve(projectDir);
|
|
46
|
+
if (noRedirect === true) {
|
|
47
|
+
return { writeRoot: root, redirect_notice: null };
|
|
48
|
+
}
|
|
49
|
+
// Layer (1): cwd IS a worktree root. Worktrees have `git rev-parse
|
|
50
|
+
// --git-dir` != `git rev-parse --git-common-dir`. Falling through to
|
|
51
|
+
// the existing behaviour writes to this worktree's .aperant/features.
|
|
52
|
+
// Fast path: git sets GIT_DIR and GIT_COMMON_DIR in the environment
|
|
53
|
+
// when running inside a worktree hook context — if both are set and
|
|
54
|
+
// differ, we are in a worktree. This env-var check is ONLY valid at
|
|
55
|
+
// Layer (1) where we ask "is this process in a worktree?"; it must
|
|
56
|
+
// NOT be used to validate an untrusted external path (Layer 2 below).
|
|
57
|
+
const envDir = process.env.GIT_DIR;
|
|
58
|
+
const envCommonDir = process.env.GIT_COMMON_DIR;
|
|
59
|
+
if (envDir && envCommonDir) {
|
|
60
|
+
if (resolve(envDir) !== resolve(envCommonDir)) {
|
|
61
|
+
return { writeRoot: root, redirect_notice: null };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else if (isWorktreeRoot(root)) {
|
|
65
|
+
return { writeRoot: root, redirect_notice: null };
|
|
66
|
+
}
|
|
67
|
+
// Layer (2): --task-id maps to an active non-terminal worktree-isolated
|
|
68
|
+
// task → redirect.
|
|
69
|
+
const stateActive = readActiveTasks(root);
|
|
70
|
+
if (taskId) {
|
|
71
|
+
const task = stateActive[taskId];
|
|
72
|
+
if (task &&
|
|
73
|
+
!TERMINAL_PHASES.has(task.lifecycle_phase) &&
|
|
74
|
+
typeof task.worktree_path === 'string' &&
|
|
75
|
+
existsSync(task.worktree_path)) {
|
|
76
|
+
const resolvedWorktree = resolve(task.worktree_path);
|
|
77
|
+
const worktreesDir = resolve(root, '.aperant', 'worktrees') + sep;
|
|
78
|
+
const isUnderProject = resolvedWorktree.startsWith(root + sep) || resolvedWorktree === root;
|
|
79
|
+
const isUnderWorktrees = resolvedWorktree.startsWith(worktreesDir);
|
|
80
|
+
// Accept only paths under the project root, under .aperant/worktrees/,
|
|
81
|
+
// or confirmed as a real git worktree by git itself. Rejects arbitrary
|
|
82
|
+
// paths injected via a tampered state.json while preserving test
|
|
83
|
+
// fixtures that use ad-hoc worktree paths outside the project tree.
|
|
84
|
+
if (!isUnderProject && !isUnderWorktrees && !isWorktreeRoot(resolvedWorktree)) {
|
|
85
|
+
return { writeRoot: root, redirect_notice: null };
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
writeRoot: resolvedWorktree,
|
|
89
|
+
redirect_notice: `features-audit redirected writes to worktree ${resolvedWorktree} (--task-id ${taskId})`,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Layer (3): multiple active worktree-isolated tasks AND no --task-id
|
|
94
|
+
// → actionable error. Single-worktree case is OK to default to root
|
|
95
|
+
// (the executor will set up the worktree's registry on first scan).
|
|
96
|
+
const activeWorktreeTasks = Object.entries(stateActive).filter(([_id, t]) => {
|
|
97
|
+
return (t &&
|
|
98
|
+
!TERMINAL_PHASES.has(t.lifecycle_phase) &&
|
|
99
|
+
typeof t.worktree_path === 'string' &&
|
|
100
|
+
existsSync(t.worktree_path));
|
|
101
|
+
});
|
|
102
|
+
if (!taskId && activeWorktreeTasks.length > 1) {
|
|
103
|
+
return {
|
|
104
|
+
error: {
|
|
105
|
+
kind: 'ambiguous-worktree-context',
|
|
106
|
+
message: 'ambiguous worktree context; pass --task-id <id> or --no-worktree-redirect',
|
|
107
|
+
active_worktree_tasks: activeWorktreeTasks.map(([id]) => id),
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
// Default: current behaviour (write into cwd).
|
|
112
|
+
return { writeRoot: root, redirect_notice: null };
|
|
113
|
+
}
|
|
114
|
+
function isWorktreeRoot(projectDir) {
|
|
115
|
+
try {
|
|
116
|
+
const dirRes = spawnSync('git', ['rev-parse', '--git-dir'], {
|
|
117
|
+
cwd: projectDir,
|
|
118
|
+
encoding: 'utf-8',
|
|
119
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
120
|
+
});
|
|
121
|
+
const commonRes = spawnSync('git', ['rev-parse', '--git-common-dir'], {
|
|
122
|
+
cwd: projectDir,
|
|
123
|
+
encoding: 'utf-8',
|
|
124
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
125
|
+
});
|
|
126
|
+
if (dirRes.status !== 0 || commonRes.status !== 0)
|
|
127
|
+
return false;
|
|
128
|
+
return (dirRes.stdout || '').trim() !== (commonRes.stdout || '').trim();
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function readActiveTasks(projectDir) {
|
|
135
|
+
const statePath = join(projectDir, '.aperant', 'state.json');
|
|
136
|
+
if (!existsSync(statePath))
|
|
137
|
+
return {};
|
|
138
|
+
try {
|
|
139
|
+
const parsed = JSON.parse(readFileSync(statePath, 'utf-8'));
|
|
140
|
+
return parsed?.active_tasks ?? {};
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return {};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
21
146
|
export function cmdFeaturesAudit(projectDir, extraArgs) {
|
|
22
147
|
if (!projectDir)
|
|
23
148
|
return err('Usage: apt-tools features-audit <project-dir> [flags]');
|
|
24
149
|
const root = resolve(projectDir);
|
|
25
150
|
const flags = parseFlags(extraArgs);
|
|
151
|
+
// BUG-025b: redirect writes to the active task's worktree when the
|
|
152
|
+
// caller is in a non-worktree cwd. See resolveWriteRoot for the
|
|
153
|
+
// 4-step resolution order.
|
|
154
|
+
const taskIdFlag = flags.get('task-id');
|
|
155
|
+
const taskId = typeof taskIdFlag === 'string' && taskIdFlag.trim() ? taskIdFlag.trim() : null;
|
|
156
|
+
const noRedirect = flags.get('no-worktree-redirect') === true;
|
|
157
|
+
const writeRootResult = resolveWriteRoot({ projectDir: root, taskId, noRedirect });
|
|
158
|
+
if (writeRootResult.error) {
|
|
159
|
+
return err(writeRootResult.error.message, 1, {
|
|
160
|
+
kind: writeRootResult.error.kind,
|
|
161
|
+
active_worktree_tasks: writeRootResult.error.active_worktree_tasks,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
const writeRoot = writeRootResult.writeRoot;
|
|
165
|
+
const redirectNotice = writeRootResult.redirect_notice;
|
|
166
|
+
if (redirectNotice) {
|
|
167
|
+
// Surface the redirect once on stderr so the executor's log carries
|
|
168
|
+
// the audit trail without polluting stdout JSON.
|
|
169
|
+
process.stderr.write(`[features-audit] ${redirectNotice}\n`);
|
|
170
|
+
}
|
|
26
171
|
const diffFiles = resolveDiffFiles(root, flags);
|
|
27
172
|
const applyStubs = flags.has('apply-stubs');
|
|
28
173
|
const format = (flags.get('format') || 'json').toLowerCase();
|
|
29
|
-
const result = auditFeatureRegistry({ projectDir:
|
|
174
|
+
const result = auditFeatureRegistry({ projectDir: writeRoot, diffFiles });
|
|
30
175
|
// Honor `.aperant/config.json.docs_policy`: only apply stubs for targets
|
|
31
176
|
// whose resolved policy is `auto-update`. This closes the skill-vs-code
|
|
32
177
|
// gap where `apt-execute` §3e-features promised `--apply-stubs` would
|
|
@@ -49,7 +194,9 @@ export function cmdFeaturesAudit(projectDir, extraArgs) {
|
|
|
49
194
|
}
|
|
50
195
|
}
|
|
51
196
|
if (eligible.length > 0) {
|
|
52
|
-
|
|
197
|
+
// BUG-025b: stubs are written to the resolved `writeRoot` (which
|
|
198
|
+
// may be the active task's worktree) rather than the invoking cwd.
|
|
199
|
+
applied_patches = applyStubPatches(writeRoot, eligible);
|
|
53
200
|
}
|
|
54
201
|
if (skipped.length > 0) {
|
|
55
202
|
skipped_patches = skipped;
|
|
@@ -61,6 +208,8 @@ export function cmdFeaturesAudit(projectDir, extraArgs) {
|
|
|
61
208
|
command: 'features-audit',
|
|
62
209
|
format: 'md',
|
|
63
210
|
markdown: renderMarkdown(result, applied_patches, skipped_patches),
|
|
211
|
+
write_root: writeRoot,
|
|
212
|
+
...(redirectNotice ? { redirect_notice: redirectNotice } : {}),
|
|
64
213
|
...result,
|
|
65
214
|
...(applied_patches ? { applied_patches } : {}),
|
|
66
215
|
...(skipped_patches ? { skipped_patches } : {}),
|
|
@@ -69,6 +218,8 @@ export function cmdFeaturesAudit(projectDir, extraArgs) {
|
|
|
69
218
|
return ok({
|
|
70
219
|
status: 'ok',
|
|
71
220
|
command: 'features-audit',
|
|
221
|
+
write_root: writeRoot,
|
|
222
|
+
...(redirectNotice ? { redirect_notice: redirectNotice } : {}),
|
|
72
223
|
...result,
|
|
73
224
|
...(applied_patches ? { applied_patches } : {}),
|
|
74
225
|
...(skipped_patches ? { skipped_patches } : {}),
|
|
@@ -82,7 +233,10 @@ function resolveDiffFiles(root, flags) {
|
|
|
82
233
|
.map((s) => s.trim())
|
|
83
234
|
.filter(Boolean);
|
|
84
235
|
}
|
|
85
|
-
const
|
|
236
|
+
const sinceRaw = flags.get('since');
|
|
237
|
+
const since = typeof sinceRaw === 'string' && /^[A-Za-z0-9/_.-]+$/.test(sinceRaw) && !sinceRaw.startsWith('-')
|
|
238
|
+
? sinceRaw
|
|
239
|
+
: null;
|
|
86
240
|
try {
|
|
87
241
|
const args = since ? ['diff', '--name-only', `${since}..HEAD`] : ['diff', 'HEAD', '--name-only'];
|
|
88
242
|
const out = execFileSync('git', args, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"features-audit.mjs","sourceRoot":"","sources":["../../../src/cli/commands/features-audit.mjs"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"features-audit.mjs","sourceRoot":"","sources":["../../../src/cli/commands/features-audit.mjs"],"names":[],"mappings":"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"}
|
|
@@ -1,2 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SECURITY: validate a component path string from `.aperant/features/*.json`
|
|
3
|
+
* before passing it to git. The registry is on-disk JSON authored by
|
|
4
|
+
* features-audit; pre-fix the orphan walker spliced this string into a
|
|
5
|
+
* shell command (`execSync(\`git rev-parse HEAD:${comp} 2>/dev/null\`)`),
|
|
6
|
+
* making a malicious entry like `foo.ts; rm -rf $HOME` a command-injection
|
|
7
|
+
* vector against any user who ran `apt-tools health-check`. The fix
|
|
8
|
+
* combines `execFileSync` (argv-form, no shell) with a strict path
|
|
9
|
+
* whitelist: relative posix paths only, no NUL / control bytes / shell
|
|
10
|
+
* metacharacters / leading `/` / `..` segments.
|
|
11
|
+
*
|
|
12
|
+
* @internal — exported for unit testing.
|
|
13
|
+
* @param {unknown} value
|
|
14
|
+
* @returns {boolean}
|
|
15
|
+
*/
|
|
16
|
+
export function isSafeComponentPath(value: unknown): boolean;
|
|
1
17
|
export function cmdHealthCheck(projectDir: any): import("../util/result.mjs").CommandResult;
|
|
2
18
|
//# sourceMappingURL=health-check.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"health-check.d.mts","sourceRoot":"","sources":["../../../src/cli/commands/health-check.mjs"],"names":[],"mappings":"AAeA,
|
|
1
|
+
{"version":3,"file":"health-check.d.mts","sourceRoot":"","sources":["../../../src/cli/commands/health-check.mjs"],"names":[],"mappings":"AAeA;;;;;;;;;;;;;;GAcG;AACH,2CAHW,OAAO,GACL,OAAO,CAYnB;AAED,4FA4OC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* commands/health-check.mjs — health-check command (C29).
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import { execFileSync } from 'node:child_process';
|
|
5
5
|
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
6
6
|
import { dirname, join, resolve } from 'node:path';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
@@ -10,6 +10,37 @@ import { err, ok } from '../util/result.mjs';
|
|
|
10
10
|
import { readSkillInstallState } from '../util/skill-installs.mjs';
|
|
11
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
12
12
|
const __dirname = dirname(__filename);
|
|
13
|
+
/**
|
|
14
|
+
* SECURITY: validate a component path string from `.aperant/features/*.json`
|
|
15
|
+
* before passing it to git. The registry is on-disk JSON authored by
|
|
16
|
+
* features-audit; pre-fix the orphan walker spliced this string into a
|
|
17
|
+
* shell command (`execSync(\`git rev-parse HEAD:${comp} 2>/dev/null\`)`),
|
|
18
|
+
* making a malicious entry like `foo.ts; rm -rf $HOME` a command-injection
|
|
19
|
+
* vector against any user who ran `apt-tools health-check`. The fix
|
|
20
|
+
* combines `execFileSync` (argv-form, no shell) with a strict path
|
|
21
|
+
* whitelist: relative posix paths only, no NUL / control bytes / shell
|
|
22
|
+
* metacharacters / leading `/` / `..` segments.
|
|
23
|
+
*
|
|
24
|
+
* @internal — exported for unit testing.
|
|
25
|
+
* @param {unknown} value
|
|
26
|
+
* @returns {boolean}
|
|
27
|
+
*/
|
|
28
|
+
export function isSafeComponentPath(value) {
|
|
29
|
+
if (typeof value !== 'string' || value.length === 0 || value.length > 512)
|
|
30
|
+
return false;
|
|
31
|
+
// Reject NUL, control bytes, and shell metacharacters that could escape
|
|
32
|
+
// argv interpretation if a downstream caller ever reverts to shell mode.
|
|
33
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: rejecting control bytes IS the security guard — see isSafeComponentPath docstring + test.
|
|
34
|
+
if (/[\x00-\x1f;&|`$<>"\\]/.test(value))
|
|
35
|
+
return false;
|
|
36
|
+
if (value.startsWith('/'))
|
|
37
|
+
return false;
|
|
38
|
+
if (value.startsWith('-'))
|
|
39
|
+
return false; // would be parsed as a flag
|
|
40
|
+
if (value.split('/').some((seg) => seg === '..' || seg === '.' || seg === ''))
|
|
41
|
+
return false;
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
13
44
|
export function cmdHealthCheck(projectDir) {
|
|
14
45
|
if (!projectDir)
|
|
15
46
|
return err('Usage: apt-tools health-check <project-dir>');
|
|
@@ -19,9 +50,10 @@ export function cmdHealthCheck(projectDir) {
|
|
|
19
50
|
// 1. Check for uncommitted changes in working tree
|
|
20
51
|
let dirtyFiles = [];
|
|
21
52
|
try {
|
|
22
|
-
const status =
|
|
53
|
+
const status = execFileSync('git', ['status', '--porcelain'], {
|
|
23
54
|
cwd: targetDir,
|
|
24
55
|
encoding: 'utf-8',
|
|
56
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
25
57
|
}).trim();
|
|
26
58
|
if (status) {
|
|
27
59
|
dirtyFiles = status.split('\n').filter(Boolean);
|
|
@@ -150,6 +182,90 @@ export function cmdHealthCheck(projectDir) {
|
|
|
150
182
|
});
|
|
151
183
|
}
|
|
152
184
|
}
|
|
185
|
+
// 6. BUG-025b — orphaned-feature-component warning. Walk
|
|
186
|
+
// `.aperant/features/*.json`; for each cited component path, verify
|
|
187
|
+
// `git rev-parse HEAD:<path>` succeeds on the main repo's HEAD. A path
|
|
188
|
+
// that resolves on a worktree branch but is missing on main is a
|
|
189
|
+
// candidate orphan (the registry entry leaked from a worktree-isolated
|
|
190
|
+
// task and was never followed by the actual component landing on main).
|
|
191
|
+
// Severity `warn` — the registry is best-effort metadata, not a build
|
|
192
|
+
// blocker. Only fires on the main repo (skipped when health-check is
|
|
193
|
+
// invoked from a worktree).
|
|
194
|
+
try {
|
|
195
|
+
const featuresDir = join(aperantDir, 'features');
|
|
196
|
+
if (existsSync(featuresDir)) {
|
|
197
|
+
// SECURITY: argv-form (no shell) — see isSafeComponentPath comment.
|
|
198
|
+
const dirRes = execFileSync('git', ['rev-parse', '--git-dir'], {
|
|
199
|
+
cwd: targetDir,
|
|
200
|
+
encoding: 'utf-8',
|
|
201
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
202
|
+
}).trim();
|
|
203
|
+
const commonRes = execFileSync('git', ['rev-parse', '--git-common-dir'], {
|
|
204
|
+
cwd: targetDir,
|
|
205
|
+
encoding: 'utf-8',
|
|
206
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
207
|
+
}).trim();
|
|
208
|
+
if (dirRes === commonRes) {
|
|
209
|
+
const orphans = [];
|
|
210
|
+
// PRF-001: one subprocess total instead of one per component.
|
|
211
|
+
// Build a Set of all tracked paths from a single ls-tree call.
|
|
212
|
+
const lsOut = execFileSync('git', ['ls-tree', '-r', 'HEAD', '--name-only'], {
|
|
213
|
+
cwd: targetDir,
|
|
214
|
+
encoding: 'utf-8',
|
|
215
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
216
|
+
}).trim();
|
|
217
|
+
const trackedPaths = new Set(lsOut
|
|
218
|
+
.split('\n')
|
|
219
|
+
.map((s) => s.trim())
|
|
220
|
+
.filter(Boolean));
|
|
221
|
+
const files = readdirSync(featuresDir).filter((f) => f.endsWith('.json') && f !== 'REGISTRY.md');
|
|
222
|
+
// QUA-006: cap total component iterations to guard against
|
|
223
|
+
// adversarially large or malformed registry files.
|
|
224
|
+
const MAX_ORPHAN_ITERATIONS = 10_000;
|
|
225
|
+
let iterCount = 0;
|
|
226
|
+
let cappedAt = null;
|
|
227
|
+
outer: for (const f of files) {
|
|
228
|
+
let area;
|
|
229
|
+
try {
|
|
230
|
+
area = JSON.parse(readFileSync(join(featuresDir, f), 'utf-8'));
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
const components = Array.isArray(area?.components) ? area.components : [];
|
|
236
|
+
for (const comp of components) {
|
|
237
|
+
if (iterCount >= MAX_ORPHAN_ITERATIONS) {
|
|
238
|
+
cappedAt = f;
|
|
239
|
+
break outer;
|
|
240
|
+
}
|
|
241
|
+
iterCount++;
|
|
242
|
+
// SECURITY: reject obviously-unsafe component strings.
|
|
243
|
+
if (!isSafeComponentPath(comp))
|
|
244
|
+
continue;
|
|
245
|
+
if (!trackedPaths.has(comp))
|
|
246
|
+
orphans.push({ area_file: f, component: comp });
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (cappedAt !== null) {
|
|
250
|
+
orphans.push({
|
|
251
|
+
area_file: cappedAt,
|
|
252
|
+
component: `(orphan scan capped at ${MAX_ORPHAN_ITERATIONS} entries — remaining components skipped)`,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
if (orphans.length > 0) {
|
|
256
|
+
issues.push({
|
|
257
|
+
type: 'orphan-feature-components',
|
|
258
|
+
severity: 'warning',
|
|
259
|
+
detail: `${orphans.length} feature component(s) cited in .aperant/features/*.json are missing on HEAD — candidates leaked from worktree-isolated tasks`,
|
|
260
|
+
orphans: orphans.slice(0, 20),
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
/* git not available or worktree — skip silently */
|
|
268
|
+
}
|
|
153
269
|
return ok({
|
|
154
270
|
status: 'ok',
|
|
155
271
|
command: 'health-check',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"health-check.mjs","sourceRoot":"","sources":["../../../src/cli/commands/health-check.mjs"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"health-check.mjs","sourceRoot":"","sources":["../../../src/cli/commands/health-check.mjs"],"names":[],"mappings":"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"}
|
|
@@ -45,6 +45,15 @@ export function parseInstallFlags(args: string[]): {
|
|
|
45
45
|
forceOverwriteCatalog: boolean;
|
|
46
46
|
keepLegacy: boolean;
|
|
47
47
|
};
|
|
48
|
+
/**
|
|
49
|
+
* Patch the template config in place with the interview answers. Leaves
|
|
50
|
+
* every key the user did NOT touch at its template default so the shape
|
|
51
|
+
* matches `/apt:setup`'s review-mode expectations.
|
|
52
|
+
*
|
|
53
|
+
* @param {Record<string, any>} config
|
|
54
|
+
* @param {InstallAnswers} answers
|
|
55
|
+
*/
|
|
56
|
+
export function applyInterviewAnswers(config: Record<string, any>, answers: InstallAnswers, targetDir?: null): void;
|
|
48
57
|
/**
|
|
49
58
|
* Main entry — invoked from bin/apt-tools.mjs.
|
|
50
59
|
*
|
|
@@ -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":"AA0EA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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;AA+wBD;;;;;;;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,CAgM3D;;;;;cA9kCa,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"}
|
|
@@ -21,6 +21,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
21
21
|
import { detectMode } from '../bootstrap/detect-mode.mjs';
|
|
22
22
|
import { deriveSidecar } from '../bootstrap/product-context-sidecar.mjs';
|
|
23
23
|
import { assertConfigWriteAllowed } from '../config/guard.mjs';
|
|
24
|
+
import { AUDIT_001_LOCAL_FIELDS, migrateDeferredFieldsToLocalConfig, writeLocalConfigField, } from '../config/load.mjs';
|
|
24
25
|
import { migrateConfigSchema, setAt } from '../config/schema-migrate.mjs';
|
|
25
26
|
import { resolveSharePolicy } from '../config/share-policy.mjs';
|
|
26
27
|
import { buildAperantGitignoreTemplate } from '../config/upgrade-gitignore.mjs';
|
|
@@ -79,6 +80,11 @@ const TRANSFORM_LOADERS = new Map(RUNTIMES.map((r) => [r.id, () => import(`../in
|
|
|
79
80
|
* @returns {{ runtimes: string[], runtimes_explicit: boolean, target: string|null, interactive: boolean, skipBootstrap: boolean, visibility?: 'solo'|'team', autonomy?: 0|1|2|3, accept_new_defaults: boolean, yes: boolean, forceRuntimes: boolean, forceOverwriteCatalog: boolean, keepLegacy: boolean }}
|
|
80
81
|
*/
|
|
81
82
|
export function parseInstallFlags(args) {
|
|
83
|
+
// C58 (0.7.2): `--kilocode` is a deprecated alias of `--kilo`. Matches
|
|
84
|
+
// the upstream package + app branding so users typing what they see on
|
|
85
|
+
// kilocode.ai get the same install as the canonical `--kilo` flag.
|
|
86
|
+
// Internal runtime id stays `kilo` (no migration — per spec ID-01).
|
|
87
|
+
args = args.map((a) => (a === '--kilocode' ? '--kilo' : a));
|
|
82
88
|
const flagSet = new Set(args.filter((a) => a.startsWith('--')));
|
|
83
89
|
let target = null;
|
|
84
90
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -659,7 +665,11 @@ async function interactiveInterview(targetDir, version, canonicalRoot, archetype
|
|
|
659
665
|
suggested.add('claude');
|
|
660
666
|
const sel = await clack.multiselect({
|
|
661
667
|
message: 'Pick the runtimes to install Aperant into',
|
|
662
|
-
options: RUNTIMES.map((r) => ({
|
|
668
|
+
options: RUNTIMES.map((r) => ({
|
|
669
|
+
value: r.id,
|
|
670
|
+
label: r.displayLabel ?? r.id,
|
|
671
|
+
hint: r.flag,
|
|
672
|
+
})),
|
|
663
673
|
initialValues: [...suggested],
|
|
664
674
|
required: true,
|
|
665
675
|
});
|
|
@@ -869,7 +879,7 @@ async function interactiveInterview(targetDir, version, canonicalRoot, archetype
|
|
|
869
879
|
* @param {Record<string, any>} config
|
|
870
880
|
* @param {InstallAnswers} answers
|
|
871
881
|
*/
|
|
872
|
-
function applyInterviewAnswers(config, answers) {
|
|
882
|
+
export function applyInterviewAnswers(config, answers, targetDir = null) {
|
|
873
883
|
if (config.autonomy) {
|
|
874
884
|
config.autonomy.default = answers.autonomyDefault;
|
|
875
885
|
}
|
|
@@ -885,8 +895,23 @@ function applyInterviewAnswers(config, answers) {
|
|
|
885
895
|
}
|
|
886
896
|
if (config.multi_model?.cross_verification) {
|
|
887
897
|
const xverify = answers.crossVerificationTools;
|
|
898
|
+
// `enabled` is project policy (is cross-verification on for this
|
|
899
|
+
// project?) — stays in shared `config.json`.
|
|
888
900
|
config.multi_model.cross_verification.enabled = xverify.length > 0;
|
|
889
|
-
|
|
901
|
+
// AUDIT-001 (0.7.2 S1) — `tools` is per-device (which CLIs are installed
|
|
902
|
+
// HERE) and MUST land in `config.local.json`, NOT shared. Strip from
|
|
903
|
+
// the template to keep the shared file clean. When `targetDir` is null
|
|
904
|
+
// (legacy callers / tests that don't go through finishInit), we leave
|
|
905
|
+
// the tools array on the shared template — backward-compatible fallback
|
|
906
|
+
// — but the writer path always passes targetDir so production lands
|
|
907
|
+
// the value in local.
|
|
908
|
+
if (targetDir) {
|
|
909
|
+
delete config.multi_model.cross_verification.tools;
|
|
910
|
+
writeLocalConfigField(targetDir, 'multi_model.cross_verification.tools', xverify);
|
|
911
|
+
}
|
|
912
|
+
else {
|
|
913
|
+
config.multi_model.cross_verification.tools = xverify;
|
|
914
|
+
}
|
|
890
915
|
}
|
|
891
916
|
config.project_type =
|
|
892
917
|
answers.archetype.type === 'brownfield' ? 'unknown' : config.project_type || 'unknown';
|
|
@@ -1333,7 +1358,11 @@ function finishInit(targetDir, installTarget, canonicalRoot, version, chosenIds,
|
|
|
1333
1358
|
withFileLock(configLockPath, () => {
|
|
1334
1359
|
if (!existsSync(configDest)) {
|
|
1335
1360
|
const template = JSON.parse(readFileSync(configTemplate, 'utf-8'));
|
|
1336
|
-
|
|
1361
|
+
// AUDIT-001 (0.7.2 S1) — pass targetDir so the cross_verification.tools
|
|
1362
|
+
// per-device field lands in `config.local.json`, not the shared
|
|
1363
|
+
// `config.json`. Matches the upgrade-path migrator which already
|
|
1364
|
+
// moves the same field via migrateDeferredFieldsToLocalConfig.
|
|
1365
|
+
applyInterviewAnswers(template, answers, targetDir);
|
|
1337
1366
|
atomicWriteJson(configDest, template);
|
|
1338
1367
|
configCreated = true;
|
|
1339
1368
|
}
|
|
@@ -1353,6 +1382,22 @@ function finishInit(targetDir, installTarget, canonicalRoot, version, chosenIds,
|
|
|
1353
1382
|
configMigrated = true;
|
|
1354
1383
|
}, { projectRoot: targetDir });
|
|
1355
1384
|
}
|
|
1385
|
+
// AUDIT-001 — UPGRADE-only migrator. Moves the 5 deferred fields out
|
|
1386
|
+
// of `config.json` into `config.local.json` on real upgrade paths
|
|
1387
|
+
// (configMigrated === true), NOT on fresh seeds — the freshly-seeded
|
|
1388
|
+
// template config.json IS the post-migration state, so the migrator
|
|
1389
|
+
// would just round-trip with no observable benefit AND would trip
|
|
1390
|
+
// the next init invocation's no-drift detection. Idempotent on
|
|
1391
|
+
// re-run. Per-field independence per ID-08 — one failure does NOT
|
|
1392
|
+
// block the others.
|
|
1393
|
+
if (configMigrated && existsSync(configDest)) {
|
|
1394
|
+
try {
|
|
1395
|
+
migrateDeferredFieldsToLocalConfig(targetDir, AUDIT_001_LOCAL_FIELDS);
|
|
1396
|
+
}
|
|
1397
|
+
catch {
|
|
1398
|
+
/* never block init on telemetry-only migrator */
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1356
1401
|
const stateTemplate = resolve(canonicalRoot, 'templates', 'state.json');
|
|
1357
1402
|
const stateDest = join(aperantDir, 'state.json');
|
|
1358
1403
|
let stateCreated = false;
|