@aperant/framework 0.7.4 → 0.8.1
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 +65 -0
- package/README.md +12 -0
- package/agents/apt-executor.md +12 -0
- package/agents/apt-team-docs-narrator.md +11 -0
- package/dist/cli/ci-watch/stop-matrix.d.mts +17 -4
- package/dist/cli/ci-watch/stop-matrix.d.mts.map +1 -1
- package/dist/cli/ci-watch/stop-matrix.mjs +41 -4
- package/dist/cli/ci-watch/stop-matrix.mjs.map +1 -1
- package/dist/cli/commands/fan-out.d.mts +2 -0
- package/dist/cli/commands/fan-out.d.mts.map +1 -0
- package/dist/cli/commands/fan-out.mjs +123 -0
- package/dist/cli/commands/fan-out.mjs.map +1 -0
- package/dist/cli/commands/init.d.mts.map +1 -1
- package/dist/cli/commands/init.mjs +67 -27
- package/dist/cli/commands/init.mjs.map +1 -1
- package/dist/cli/commands/modes.d.mts +22 -0
- package/dist/cli/commands/modes.d.mts.map +1 -1
- package/dist/cli/commands/modes.mjs +4 -1
- package/dist/cli/commands/modes.mjs.map +1 -1
- package/dist/cli/commands/pr-review-audit-fixer.d.mts.map +1 -1
- package/dist/cli/commands/pr-review-audit-fixer.mjs +86 -6
- package/dist/cli/commands/pr-review-audit-fixer.mjs.map +1 -1
- package/dist/cli/commands/roadmap.d.mts +1 -1
- package/dist/cli/commands/roadmap.d.mts.map +1 -1
- package/dist/cli/commands/roadmap.mjs +47 -4
- package/dist/cli/commands/roadmap.mjs.map +1 -1
- package/dist/cli/commands/task.d.mts.map +1 -1
- package/dist/cli/commands/task.mjs +15 -0
- package/dist/cli/commands/task.mjs.map +1 -1
- package/dist/cli/commands/validate-evidence.d.mts +1 -1
- package/dist/cli/commands/validate-evidence.d.mts.map +1 -1
- package/dist/cli/commands/validate-evidence.mjs +72 -47
- package/dist/cli/commands/validate-evidence.mjs.map +1 -1
- package/dist/cli/commands/wave.d.mts.map +1 -1
- package/dist/cli/commands/wave.mjs +91 -61
- package/dist/cli/commands/wave.mjs.map +1 -1
- package/dist/cli/commands/worktree.mjs +62 -3
- package/dist/cli/commands/worktree.mjs.map +1 -1
- package/dist/cli/config/load.mjs +1 -1
- package/dist/cli/config/load.mjs.map +1 -1
- package/dist/cli/config/parallelization.d.mts +14 -0
- package/dist/cli/config/parallelization.d.mts.map +1 -0
- package/dist/cli/config/parallelization.mjs +19 -0
- package/dist/cli/config/parallelization.mjs.map +1 -0
- package/dist/cli/coordination/agent-identity.d.mts.map +1 -1
- package/dist/cli/coordination/agent-identity.mjs +7 -1
- package/dist/cli/coordination/agent-identity.mjs.map +1 -1
- package/dist/cli/design/frontmatter-schema.d.mts +4 -4
- 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/install/cli-to-install-root.d.mts.map +1 -1
- package/dist/cli/install/cli-to-install-root.mjs +0 -2
- package/dist/cli/install/cli-to-install-root.mjs.map +1 -1
- package/dist/cli/install/runtime-detect.d.mts +2 -6
- package/dist/cli/install/runtime-detect.d.mts.map +1 -1
- package/dist/cli/install/runtime-detect.mjs +8 -3
- package/dist/cli/install/runtime-detect.mjs.map +1 -1
- package/dist/cli/install/transforms/pi.d.mts +6 -0
- package/dist/cli/install/transforms/pi.d.mts.map +1 -0
- package/dist/cli/install/transforms/pi.mjs +37 -0
- package/dist/cli/install/transforms/pi.mjs.map +1 -0
- package/dist/cli/personas/sidecar.d.mts +1 -1
- package/dist/cli/roadmap/convex-mirror.d.mts +21 -0
- package/dist/cli/roadmap/convex-mirror.d.mts.map +1 -0
- package/dist/cli/roadmap/convex-mirror.mjs +133 -0
- package/dist/cli/roadmap/convex-mirror.mjs.map +1 -0
- package/dist/cli/roadmap/lifecycle.d.mts +1 -1
- package/dist/cli/roadmap/lifecycle.d.mts.map +1 -1
- package/dist/cli/roadmap/lifecycle.mjs +54 -0
- package/dist/cli/roadmap/lifecycle.mjs.map +1 -1
- package/dist/cli/roadmap/rollup.d.mts +2 -2
- package/dist/cli/wfrun/unit-shape.d.mts +27 -0
- package/dist/cli/wfrun/unit-shape.d.mts.map +1 -0
- package/dist/cli/wfrun/unit-shape.mjs +38 -0
- package/dist/cli/wfrun/unit-shape.mjs.map +1 -0
- package/dist/plugin/.claude-plugin/plugin.json +2 -1
- package/dist/plugin/agents/apt-executor.md +12 -0
- package/dist/plugin/agents/apt-team-docs-narrator.md +11 -0
- package/dist/plugin/skills/apt/SKILL.md +3 -2
- package/dist/plugin/skills/apt-execute/SKILL.md +13 -0
- package/dist/plugin/skills/apt-fan-out/SKILL.md +124 -0
- package/dist/plugin/skills/apt-pr-review/SKILL.md +8 -1
- package/dist/plugin/skills/apt-review/SKILL.md +4 -0
- package/dist/plugin/skills/apt-run/SKILL.md +20 -5
- package/dist/plugin/skills/apt-spar/SKILL.md +12 -9
- package/dist/plugin/skills/apt-verify/SKILL.md +11 -0
- package/dist/plugin/skills/apt-watch-ci/SKILL.md +2 -2
- package/dist/schemas/quick-task.d.ts +17 -17
- package/package.json +133 -133
- package/skills/apt/SKILL.md +3 -2
- package/skills/apt-execute/SKILL.md +13 -0
- package/skills/apt-fan-out/SKILL.md +124 -0
- package/skills/apt-review/SKILL.md +4 -0
- package/skills/apt-run/SKILL.md +20 -5
- package/skills/apt-spar/SKILL.md +12 -9
- package/skills/apt-verify/SKILL.md +11 -0
- package/skills/apt-watch-ci/SKILL.md +2 -2
- package/src/cli/ci-watch/stop-matrix.mjs +44 -4
- package/src/cli/commands/fan-out.mjs +129 -0
- package/src/cli/commands/init.mjs +66 -27
- package/src/cli/commands/modes.mjs +4 -1
- package/src/cli/commands/pr-review-audit-fixer.mjs +82 -6
- package/src/cli/commands/roadmap.mjs +48 -4
- package/src/cli/commands/task.mjs +15 -0
- package/src/cli/commands/validate-evidence.mjs +75 -50
- package/src/cli/commands/wave.mjs +91 -58
- package/src/cli/commands/worktree.mjs +56 -3
- package/src/cli/config/load.mjs +1 -1
- package/src/cli/config/parallelization.mjs +18 -0
- package/src/cli/coordination/agent-identity.mjs +9 -1
- package/src/cli/dispatch.mjs +2 -0
- package/src/cli/install/cli-to-install-root.mjs +0 -2
- package/src/cli/install/runtime-detect.mjs +8 -3
- package/src/cli/install/transforms/pi.mjs +37 -0
- package/src/cli/roadmap/convex-mirror.mjs +131 -0
- package/src/cli/roadmap/lifecycle.mjs +48 -0
- package/src/cli/wfrun/unit-shape.mjs +39 -0
- package/templates/config.json +1 -1
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run the convex-mirror call.
|
|
3
|
+
*
|
|
4
|
+
* @param {string} projectDir
|
|
5
|
+
* @param {{scope: string, phaseId: string, anchorId: string, proseBody: string, mergedPr?: number, baseUrl?: string, fetchImpl?: typeof fetch}} args
|
|
6
|
+
* @returns {Promise<{status: 'ok'|'queued'|'error', code?: number, message?: string}>}
|
|
7
|
+
*/
|
|
8
|
+
export function runConvexMirror(projectDir: string, args: {
|
|
9
|
+
scope: string;
|
|
10
|
+
phaseId: string;
|
|
11
|
+
anchorId: string;
|
|
12
|
+
proseBody: string;
|
|
13
|
+
mergedPr?: number;
|
|
14
|
+
baseUrl?: string;
|
|
15
|
+
fetchImpl?: typeof fetch;
|
|
16
|
+
}): Promise<{
|
|
17
|
+
status: "ok" | "queued" | "error";
|
|
18
|
+
code?: number;
|
|
19
|
+
message?: string;
|
|
20
|
+
}>;
|
|
21
|
+
//# sourceMappingURL=convex-mirror.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"convex-mirror.d.mts","sourceRoot":"","sources":["../../../src/cli/roadmap/convex-mirror.mjs"],"names":[],"mappings":"AAiDA;;;;;;GAMG;AACH,4CAJW,MAAM,QACN;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;CAAC,GAClI,OAAO,CAAC;IAAC,MAAM,EAAE,IAAI,GAAC,QAAQ,GAAC,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAC,CAAC,CA4ErF"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slice 5 ST14 / AC10 / ID-06 — `apt-tools roadmap convex-mirror`.
|
|
3
|
+
*
|
|
4
|
+
* Helper that POSTs the rendered narrator prose to the Next.js
|
|
5
|
+
* /api/roadmap/narrator-mirror endpoint. Markdown is canonical
|
|
6
|
+
* (ADR-0006); this call is the Convex mirror.
|
|
7
|
+
*
|
|
8
|
+
* Fail-soft contract:
|
|
9
|
+
* - 200 {mirrored:true} → exit 0, status='ok'
|
|
10
|
+
* - 200 {mirrored:false,queued:true} → exit 0, status='queued'
|
|
11
|
+
* - 401 → exit 1, status='error' (auth misconfig)
|
|
12
|
+
* - 4xx (other than 401) → exit 1, status='error'
|
|
13
|
+
* - 5xx / network error → exit 0, status='queued', appends a
|
|
14
|
+
* pending JSONL row to
|
|
15
|
+
* .aperant/digests/narrator-mirror-pending.jsonl
|
|
16
|
+
* so a follow-up narrator invocation
|
|
17
|
+
* can replay locally if the cron
|
|
18
|
+
* drainer hasn't caught up.
|
|
19
|
+
*
|
|
20
|
+
* Token resolution order (per ID-07):
|
|
21
|
+
* 1. process.env.MCP_NARRATOR_TOKEN
|
|
22
|
+
* 2. .aperant/team.json :: narratorMirrorToken
|
|
23
|
+
*/
|
|
24
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs';
|
|
25
|
+
import { dirname, join } from 'node:path';
|
|
26
|
+
function resolveToken(projectDir) {
|
|
27
|
+
const envToken = process.env.MCP_NARRATOR_TOKEN;
|
|
28
|
+
if (envToken && envToken.length > 0)
|
|
29
|
+
return envToken;
|
|
30
|
+
const teamJsonPath = join(projectDir, '.aperant', 'team.json');
|
|
31
|
+
if (!existsSync(teamJsonPath))
|
|
32
|
+
return null;
|
|
33
|
+
try {
|
|
34
|
+
const parsed = JSON.parse(readFileSync(teamJsonPath, 'utf-8'));
|
|
35
|
+
if (typeof parsed.narratorMirrorToken === 'string' && parsed.narratorMirrorToken.length > 0) {
|
|
36
|
+
return parsed.narratorMirrorToken;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// Bad JSON — fall through to null
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
function appendPendingJsonl(projectDir, row) {
|
|
45
|
+
const path = join(projectDir, '.aperant', 'digests', 'narrator-mirror-pending.jsonl');
|
|
46
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
47
|
+
appendFileSync(path, `${JSON.stringify(row)}\n`);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Run the convex-mirror call.
|
|
51
|
+
*
|
|
52
|
+
* @param {string} projectDir
|
|
53
|
+
* @param {{scope: string, phaseId: string, anchorId: string, proseBody: string, mergedPr?: number, baseUrl?: string, fetchImpl?: typeof fetch}} args
|
|
54
|
+
* @returns {Promise<{status: 'ok'|'queued'|'error', code?: number, message?: string}>}
|
|
55
|
+
*/
|
|
56
|
+
export async function runConvexMirror(projectDir, args) {
|
|
57
|
+
const { scope, phaseId, anchorId, proseBody, mergedPr, baseUrl, fetchImpl } = args;
|
|
58
|
+
if (!scope || !phaseId || !anchorId || typeof proseBody !== 'string') {
|
|
59
|
+
return { status: 'error', message: 'missing required: scope, phaseId, anchorId, proseBody' };
|
|
60
|
+
}
|
|
61
|
+
const token = resolveToken(projectDir);
|
|
62
|
+
if (!token) {
|
|
63
|
+
return {
|
|
64
|
+
status: 'error',
|
|
65
|
+
message: 'No narrator token (set MCP_NARRATOR_TOKEN or .aperant/team.json::narratorMirrorToken)',
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const url = `${(baseUrl || process.env.APERANT_BASE_URL || 'https://app.aperant.com').replace(/\/$/, '')}/api/roadmap/narrator-mirror`;
|
|
69
|
+
const body = {
|
|
70
|
+
scope,
|
|
71
|
+
phaseId,
|
|
72
|
+
narratorAnchorId: anchorId,
|
|
73
|
+
narratorProse: proseBody,
|
|
74
|
+
mergedPr,
|
|
75
|
+
};
|
|
76
|
+
const f = fetchImpl ?? fetch;
|
|
77
|
+
let res;
|
|
78
|
+
try {
|
|
79
|
+
res = await f(url, {
|
|
80
|
+
method: 'POST',
|
|
81
|
+
headers: {
|
|
82
|
+
Authorization: `Bearer ${token}`,
|
|
83
|
+
'Content-Type': 'application/json',
|
|
84
|
+
},
|
|
85
|
+
body: JSON.stringify(body),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
catch (e) {
|
|
89
|
+
// Network failure → fail-soft queue.
|
|
90
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
91
|
+
appendPendingJsonl(projectDir, {
|
|
92
|
+
...body,
|
|
93
|
+
lastError: `network: ${msg}`,
|
|
94
|
+
ts: new Date().toISOString(),
|
|
95
|
+
});
|
|
96
|
+
return { status: 'queued', message: `network error: ${msg}` };
|
|
97
|
+
}
|
|
98
|
+
if (res.status === 401) {
|
|
99
|
+
return { status: 'error', code: 401, message: 'auth: invalid or missing token' };
|
|
100
|
+
}
|
|
101
|
+
if (res.status >= 500) {
|
|
102
|
+
// 5xx → fail-soft queue.
|
|
103
|
+
appendPendingJsonl(projectDir, {
|
|
104
|
+
...body,
|
|
105
|
+
lastError: `http ${res.status}`,
|
|
106
|
+
ts: new Date().toISOString(),
|
|
107
|
+
});
|
|
108
|
+
return { status: 'queued', code: res.status, message: 'server error — enqueued' };
|
|
109
|
+
}
|
|
110
|
+
if (res.status >= 400) {
|
|
111
|
+
let detail = '';
|
|
112
|
+
try {
|
|
113
|
+
detail = await res.text();
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// ignore
|
|
117
|
+
}
|
|
118
|
+
return { status: 'error', code: res.status, message: detail || `http ${res.status}` };
|
|
119
|
+
}
|
|
120
|
+
// 200/2xx
|
|
121
|
+
let parsed;
|
|
122
|
+
try {
|
|
123
|
+
parsed = await res.json();
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
parsed = { mirrored: true, queued: false };
|
|
127
|
+
}
|
|
128
|
+
if (parsed?.queued) {
|
|
129
|
+
return { status: 'queued', code: res.status, message: 'enqueued server-side' };
|
|
130
|
+
}
|
|
131
|
+
return { status: 'ok', code: res.status };
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=convex-mirror.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"convex-mirror.mjs","sourceRoot":"","sources":["../../../src/cli/roadmap/convex-mirror.mjs"],"names":[],"mappings":"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"}
|
|
@@ -45,7 +45,7 @@ export function applyLifecyclePhaseSideEffect(targetDir: any, task: any, spec: a
|
|
|
45
45
|
commit: string | null;
|
|
46
46
|
rollup: {
|
|
47
47
|
before: any;
|
|
48
|
-
after: "
|
|
48
|
+
after: "planned" | "completed" | "blocked" | "in-progress" | null;
|
|
49
49
|
changed: boolean;
|
|
50
50
|
milestone_id: any;
|
|
51
51
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecycle.d.mts","sourceRoot":"","sources":["../../../src/cli/roadmap/lifecycle.mjs"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"lifecycle.d.mts","sourceRoot":"","sources":["../../../src/cli/roadmap/lifecycle.mjs"],"names":[],"mappings":"AAkBA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;SA8GC"}
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lifecycle.mjs — shared lifecycle-phase side-effect helper (C29).
|
|
3
|
+
*
|
|
4
|
+
* FRAMEWORK-BUG-033 defect 1/5: when `spec.to === 'completed'`, the helper
|
|
5
|
+
* enumerates sibling tasks in the same scope+milestone+phase and aborts
|
|
6
|
+
* the flip (returns null — idempotent no-op shape) when any sibling
|
|
7
|
+
* remains open (lifecycle_phase !== 'closed'). The guard lives inside the
|
|
8
|
+
* helper so every caller (`task update --lifecycle-phase`, `task close
|
|
9
|
+
* --verdict approved`, the drain wrapper, and `task close-merged`
|
|
10
|
+
* transitively via its `task close` child) inherits it.
|
|
3
11
|
*/
|
|
12
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
13
|
+
import { join } from 'node:path';
|
|
4
14
|
import { loadRoadmapForScope, saveRoadmapForScope } from './io.mjs';
|
|
5
15
|
import { applyRollup } from './rollup.mjs';
|
|
6
16
|
import { syncRoadmapInProcess } from './sync.mjs';
|
|
@@ -55,6 +65,50 @@ export function applyLifecyclePhaseSideEffect(targetDir, task, spec, reasonPrefi
|
|
|
55
65
|
// From-state guard: idempotent no-op when phase is not at `spec.from`.
|
|
56
66
|
if (phase.status !== spec.from)
|
|
57
67
|
return null;
|
|
68
|
+
// FRAMEWORK-BUG-033 defect 1/5 — sibling-count guard. Only gates the
|
|
69
|
+
// `→ completed` direction (other transitions like `→ in-progress` are
|
|
70
|
+
// unaffected per ID-02 / AC7). Enumerates state.active_tasks filtered by
|
|
71
|
+
// scope+milestone+phase. Any open sibling (lifecycle_phase !== 'closed'
|
|
72
|
+
// and id !== task.id) blocks the flip with the same idempotent no-op
|
|
73
|
+
// shape as the from-state guard above.
|
|
74
|
+
//
|
|
75
|
+
// State source: caller may pass `opts.stateBlob` when already holding
|
|
76
|
+
// `withFileLock(state.json)` to avoid a re-read race; otherwise we read
|
|
77
|
+
// state.json from disk once. On the `task close` path the task has
|
|
78
|
+
// already been deleted from state.active_tasks before this helper runs,
|
|
79
|
+
// so the `t.id !== task.id` filter is a defensive no-op there.
|
|
80
|
+
if (spec.to === 'completed') {
|
|
81
|
+
let stateBlob = opts.stateBlob;
|
|
82
|
+
if (!stateBlob) {
|
|
83
|
+
const statePath = join(targetDir, '.aperant', 'state.json');
|
|
84
|
+
if (existsSync(statePath)) {
|
|
85
|
+
try {
|
|
86
|
+
stateBlob = JSON.parse(readFileSync(statePath, 'utf-8'));
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Fail-secure: cannot verify sibling state → assume siblings might exist → abort the flip.
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const activeTasks = stateBlob?.active_tasks || {};
|
|
95
|
+
for (const sibling of Object.values(activeTasks)) {
|
|
96
|
+
if (!sibling || typeof sibling !== 'object')
|
|
97
|
+
continue;
|
|
98
|
+
if (sibling.scope !== scope)
|
|
99
|
+
continue;
|
|
100
|
+
if (sibling.milestone_id !== task.milestone_id)
|
|
101
|
+
continue;
|
|
102
|
+
if (sibling.phase_id !== task.phase_id)
|
|
103
|
+
continue;
|
|
104
|
+
if (sibling.id === task.id)
|
|
105
|
+
continue;
|
|
106
|
+
if (sibling.lifecycle_phase === 'closed')
|
|
107
|
+
continue;
|
|
108
|
+
// Open sibling — abort the flip.
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
58
112
|
const transition = {
|
|
59
113
|
op: reasonPrefix,
|
|
60
114
|
scope,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecycle.mjs","sourceRoot":"","sources":["../../../src/cli/roadmap/lifecycle.mjs"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"lifecycle.mjs","sourceRoot":"","sources":["../../../src/cli/roadmap/lifecycle.mjs"],"names":[],"mappings":"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"}
|
|
@@ -16,7 +16,7 @@ export function rollupMilestoneStatus(milestone: object): "completed" | "in-prog
|
|
|
16
16
|
*/
|
|
17
17
|
export function applyRollup(roadmap: any, milestoneId: any): {
|
|
18
18
|
before: any;
|
|
19
|
-
after: "
|
|
19
|
+
after: "planned" | "completed" | "blocked" | "in-progress" | null;
|
|
20
20
|
changed: boolean;
|
|
21
21
|
milestone_id: any;
|
|
22
22
|
};
|
|
@@ -31,7 +31,7 @@ export function applyRollup(roadmap: any, milestoneId: any): {
|
|
|
31
31
|
export function writeRollup(projectDir: any, scope: any, milestoneId: any): {
|
|
32
32
|
committed: boolean;
|
|
33
33
|
before: any;
|
|
34
|
-
after: "
|
|
34
|
+
after: "planned" | "completed" | "blocked" | "in-progress" | null;
|
|
35
35
|
changed: boolean;
|
|
36
36
|
milestone_id: any;
|
|
37
37
|
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wfrun/unit-shape.mjs — FRAMEWORK-BUG-033 capability gap (ID-03).
|
|
3
|
+
*
|
|
4
|
+
* Normalize a wave-engine plan blob to the unit-agnostic
|
|
5
|
+
* `{units, unit_kind}` shape. Two input forms accepted:
|
|
6
|
+
* - Subtask-shaped (back-compat): `{subtasks: [...]}`.
|
|
7
|
+
* - Task-shaped (new): `{unit_kind: "task", tasks: [...]}`.
|
|
8
|
+
*
|
|
9
|
+
* Internally the wave engine operates purely on `{id, dependencies,
|
|
10
|
+
* files}` records — only the I/O boundary cares about the rename.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Detect whether a plan blob is task-shaped, subtask-shaped, or invalid.
|
|
14
|
+
*
|
|
15
|
+
* @param {unknown} plan
|
|
16
|
+
* @returns {{ units: Array<{id: string, dependencies: string[], files?: string[]}>, unit_kind: 'subtask' | 'task', error: string | null }}
|
|
17
|
+
*/
|
|
18
|
+
export function normalizeToUnits(plan: unknown): {
|
|
19
|
+
units: Array<{
|
|
20
|
+
id: string;
|
|
21
|
+
dependencies: string[];
|
|
22
|
+
files?: string[];
|
|
23
|
+
}>;
|
|
24
|
+
unit_kind: "subtask" | "task";
|
|
25
|
+
error: string | null;
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=unit-shape.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unit-shape.d.mts","sourceRoot":"","sources":["../../../src/cli/wfrun/unit-shape.mjs"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH;;;;;GAKG;AACH,uCAHW,OAAO,GACL;IAAE,KAAK,EAAE,KAAK,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;KAAC,CAAC,CAAC;IAAC,SAAS,EAAE,SAAS,GAAG,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAsBzI"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wfrun/unit-shape.mjs — FRAMEWORK-BUG-033 capability gap (ID-03).
|
|
3
|
+
*
|
|
4
|
+
* Normalize a wave-engine plan blob to the unit-agnostic
|
|
5
|
+
* `{units, unit_kind}` shape. Two input forms accepted:
|
|
6
|
+
* - Subtask-shaped (back-compat): `{subtasks: [...]}`.
|
|
7
|
+
* - Task-shaped (new): `{unit_kind: "task", tasks: [...]}`.
|
|
8
|
+
*
|
|
9
|
+
* Internally the wave engine operates purely on `{id, dependencies,
|
|
10
|
+
* files}` records — only the I/O boundary cares about the rename.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Detect whether a plan blob is task-shaped, subtask-shaped, or invalid.
|
|
14
|
+
*
|
|
15
|
+
* @param {unknown} plan
|
|
16
|
+
* @returns {{ units: Array<{id: string, dependencies: string[], files?: string[]}>, unit_kind: 'subtask' | 'task', error: string | null }}
|
|
17
|
+
*/
|
|
18
|
+
export function normalizeToUnits(plan) {
|
|
19
|
+
if (!plan || typeof plan !== 'object') {
|
|
20
|
+
return { units: [], unit_kind: 'subtask', error: 'Plan must be an object' };
|
|
21
|
+
}
|
|
22
|
+
const isTaskShape = plan.unit_kind === 'task' || (Array.isArray(plan.tasks) && !Array.isArray(plan.subtasks));
|
|
23
|
+
if (isTaskShape) {
|
|
24
|
+
if (!Array.isArray(plan.tasks)) {
|
|
25
|
+
return { units: [], unit_kind: 'task', error: 'Plan must contain a "tasks" array' };
|
|
26
|
+
}
|
|
27
|
+
return { units: plan.tasks, unit_kind: 'task', error: null };
|
|
28
|
+
}
|
|
29
|
+
if (!Array.isArray(plan.subtasks)) {
|
|
30
|
+
return {
|
|
31
|
+
units: [],
|
|
32
|
+
unit_kind: 'subtask',
|
|
33
|
+
error: 'Plan must contain a "subtasks" array',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return { units: plan.subtasks, unit_kind: 'subtask', error: null };
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=unit-shape.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unit-shape.mjs","sourceRoot":"","sources":["../../../src/cli/wfrun/unit-shape.mjs"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAI;IACpC,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QACvC,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,wBAAwB,EAAE,CAAA;IAC5E,CAAC;IACD,MAAM,WAAW,GAChB,IAAI,CAAC,SAAS,KAAK,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAA;IAC1F,IAAI,WAAW,EAAE,CAAC;QACjB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAChC,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,mCAAmC,EAAE,CAAA;QACpF,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;IAC7D,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACnC,OAAO;YACN,KAAK,EAAE,EAAE;YACT,SAAS,EAAE,SAAS;YACpB,KAAK,EAAE,sCAAsC;SAC7C,CAAA;IACF,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;AACnE,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aperant",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "AI coding framework — composable skills for planning, executing, verifying, and reviewing code with any LLM provider. Works as Claude Code commands, Codex tasks, or programmatically.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Mikalsen AI",
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"skills/apt-discuss",
|
|
35
35
|
"skills/apt-docs",
|
|
36
36
|
"skills/apt-execute",
|
|
37
|
+
"skills/apt-fan-out",
|
|
37
38
|
"skills/apt-improve",
|
|
38
39
|
"skills/apt-mockup",
|
|
39
40
|
"skills/apt-pause",
|
|
@@ -46,6 +46,18 @@ node packages/framework/bin/apt-tools.mjs gate check . --task-id {task-id} --ski
|
|
|
46
46
|
|
|
47
47
|
If `status == "fail"`, STOP. Report the failed gates verbatim and exit — do NOT auto-apply `--force`. Forcing is a human decision; the user can re-invoke with `--force --reason "<explanation>"` to bypass (the override is logged to `.aperant/events/{today}.jsonl`).
|
|
48
48
|
|
|
49
|
+
## 0a. Fan-out worker identity (FRAMEWORK-BUG-033 defect 3)
|
|
50
|
+
|
|
51
|
+
If your spawn prompt contains both `batch_id` and `task_id` (i.e. you are a fan-out worker subagent dispatched by `/apt:fan-out`), export `APT_AGENT_ID` in EVERY `Bash` invocation that runs `apt-tools ...`:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
APT_AGENT_ID=fanout-${batch_id}-${task_id} node packages/framework/bin/apt-tools.mjs <command> ...
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
This pins your `team-status/{agentId}.json` file across short-lived CLI invocations. Without it, each `apt-tools` call gets a fresh `hostname-pid` agentId (per `agent-identity.mjs:86`), the previous pid dies, and `listTeamStatus({ includeStale: false })` filters healthy workers out between invocations — the team-status dashboard flickers into emptiness instead of showing 4 stable fan-out workers.
|
|
58
|
+
|
|
59
|
+
Non-fan-out invocations of this agent receive no `batch_id` and skip this export — the legacy `hostname-pid` path remains correct for single-task work.
|
|
60
|
+
|
|
49
61
|
## 1. Load Context
|
|
50
62
|
|
|
51
63
|
1. Read `CLAUDE.md` and `AGENTS.md` if they exist
|
|
@@ -72,6 +72,17 @@ If the parent spawned you without a mode you recognize, write an error line to `
|
|
|
72
72
|
rendered table. If present, REPLACE the content between anchors (never
|
|
73
73
|
append — this is how re-runs stay idempotent).
|
|
74
74
|
7. Call `apt-tools roadmap sync . --reason 'narrator:phase-ship:<phase_id>'`.
|
|
75
|
+
8. **Slice 5 / AC14 / ADR-0006 dual-write.** Write the rendered prose
|
|
76
|
+
block to a temp file (e.g. `mktemp -t narrator-prose-XXXXXX.md`), then
|
|
77
|
+
run
|
|
78
|
+
`apt-tools roadmap convex-mirror . --scope <scope> --phase <phase_id> --anchor-id phase-ship:<phase_id> --prose-path <tempfile>`
|
|
79
|
+
(add `--merged-pr <pr-number>` when known). The markdown write from
|
|
80
|
+
step 7 is canonical (ADR-0006); this step is the Convex mirror.
|
|
81
|
+
Fail-soft contract:
|
|
82
|
+
- exit 0 with `status:ok` or `status:queued` → continue normally
|
|
83
|
+
- exit non-zero with `status:error` → append the error message to
|
|
84
|
+
`.aperant/digests/errors.log` and continue (do NOT block on Convex
|
|
85
|
+
failures; the in-process cron drainer reconciles within 5 min)
|
|
75
86
|
|
|
76
87
|
## Mode 2: `decision-merge` — merge a recorded decision into the roadmap
|
|
77
88
|
|
|
@@ -41,8 +41,9 @@ you must follow when classifying.
|
|
|
41
41
|
- **Config:** `.aperant/config.json` — project preferences
|
|
42
42
|
- **State:** `.aperant/state.json` — execution state
|
|
43
43
|
- **Task directories:** `.aperant/tasks/{task-id}/` — each task gets isolated artifacts
|
|
44
|
-
- **
|
|
45
|
-
- **
|
|
44
|
+
- **Claude surface:** `.claude/skills/`, `.claude/agents/`
|
|
45
|
+
- **Codex surface:** `.codex/commands/`, `.codex/agents/`
|
|
46
|
+
- **Pi surface:** `.pi/prompts/`, `.pi/skills/`
|
|
46
47
|
</your_environment>
|
|
47
48
|
|
|
48
49
|
<mandatory_first_step>
|
|
@@ -453,6 +453,19 @@ After all subtasks are completed:
|
|
|
453
453
|
node packages/framework/bin/apt-tools.mjs commit "build: all subtasks complete" --files implementation_plan.json build-progress.json
|
|
454
454
|
```
|
|
455
455
|
|
|
456
|
+
### Mark lifecycle phase as `verifying`
|
|
457
|
+
|
|
458
|
+
Flip the task's lifecycle phase at the success-end boundary so the next stage
|
|
459
|
+
(`/apt:verify`) finds the task in the correct from-state. Mirror the same CLI
|
|
460
|
+
shape as the start-boundary call in Section 2 — quick tasks (no phase linkage)
|
|
461
|
+
silently no-op, and the update is idempotent so it is safe to re-run on
|
|
462
|
+
`--continue`. Skip this step if any subtask ended in `"blocked"` — only the
|
|
463
|
+
all-subtasks-completed branch should advance the phase.
|
|
464
|
+
|
|
465
|
+
```bash
|
|
466
|
+
node packages/framework/bin/apt-tools.mjs task update . --id {task-id} --lifecycle-phase verifying
|
|
467
|
+
```
|
|
468
|
+
|
|
456
469
|
Output:
|
|
457
470
|
```
|
|
458
471
|
Execution complete:
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: apt:fan-out
|
|
3
|
+
description: "Cross-task fan-out — spawn N apt-executor workers from one conductor terminal, each in its own worktree"
|
|
4
|
+
apt-skill-version: {{APT_VERSION}}
|
|
5
|
+
stage: execute
|
|
6
|
+
intent: x-fan-out
|
|
7
|
+
when_to_use: "You have N pre-planned, independent tasks (no file overlap, no cross-dependencies) and want to run them concurrently from a single host CLI session."
|
|
8
|
+
user_invocable: true
|
|
9
|
+
internal: false
|
|
10
|
+
spawns_agent: true
|
|
11
|
+
agent_name: "apt-executor"
|
|
12
|
+
task_context: require-existing
|
|
13
|
+
default_execution_mode: auto
|
|
14
|
+
execution_modes:
|
|
15
|
+
- auto
|
|
16
|
+
- step
|
|
17
|
+
allowed-tools: "Bash, Read, Write, Task"
|
|
18
|
+
argument-hint: "apt:fan-out --tasks <id,id,id> [--phases <m/p,m/p>] [--unblocked]"
|
|
19
|
+
gates: []
|
|
20
|
+
---
|
|
21
|
+
<objective>
|
|
22
|
+
Run N pre-planned Aperant tasks concurrently from one conductor terminal. This skill is a thin orchestrator over the generalized wave engine (`unit_kind: "task"` path, see ADR-0003) and the existing primitives: `apt-tools worktree create --task`, `apt-tools lock claim`, `apt-tools event append`, and the `parallelization.max_tasks` quota. No daemon, no job queue, no in-skill scheduling. The host CLI's main thread is the conductor (ADR-0001); each spawned apt-executor subagent runs in its own worktree (ADR-0002).
|
|
23
|
+
|
|
24
|
+
This is the "I have 16 unblocked tasks in this phase, run them all" surface. It replaces the old workaround of opening 16 sibling terminals manually.
|
|
25
|
+
</objective>
|
|
26
|
+
|
|
27
|
+
<your_environment>
|
|
28
|
+
- **Working directory:** Project root (where you were invoked).
|
|
29
|
+
- **apt-tools path:** `node packages/framework/bin/apt-tools.mjs` (or `node .aperant/deps/node_modules/@aperant/framework/bin/apt-tools.mjs` when running from an install mirror).
|
|
30
|
+
- **Concurrency cap:** `parallelization.max_tasks` from `.aperant/config.json` (default 4, no paternalism cap per D-07). Read via `cli/config/parallelization.mjs:getMaxTasks(config)`.
|
|
31
|
+
- **Worker identity:** Each spawned worker exports `APT_AGENT_ID=fanout-${batch_id}-${task_id}` before any `apt-tools ...` invocation (per FRAMEWORK-BUG-033 defect 3). This pins their `team-status/{agentId}.json` rows across short-lived CLI calls.
|
|
32
|
+
- **Constitution:** Read `AGENTS.md` if it exists.
|
|
33
|
+
</your_environment>
|
|
34
|
+
|
|
35
|
+
<state_files>
|
|
36
|
+
## State Files
|
|
37
|
+
|
|
38
|
+
**Reads:**
|
|
39
|
+
- `.aperant/state.json` — `active_tasks[*]` filtered when `--phases` or `--unblocked` resolves the task list.
|
|
40
|
+
- `.aperant/roadmap/{scope}/milestones.json` — milestone/phase membership for `--phases`.
|
|
41
|
+
- `.aperant/config.json:parallelization.max_tasks` — concurrency cap.
|
|
42
|
+
|
|
43
|
+
**Writes (indirectly via spawned workers):**
|
|
44
|
+
- `.aperant/team-status/fanout-${batch_id}-${task_id}.json` — one stable file per worker (per FRAMEWORK-BUG-033 defect 3 fix).
|
|
45
|
+
- `.aperant/state.json:active_tasks[*].assigned_to` — workers stamp their `APT_AGENT_ID` on claim (per FRAMEWORK-BUG-033 defect 2 fix).
|
|
46
|
+
- `.aperant/events/{today}.jsonl` — `task-claimed`, `wave-started`, `wave-completed` events.
|
|
47
|
+
|
|
48
|
+
**No filesystem state of its own.** The skill body is pure orchestration glue; all heavy lifting reuses existing primitives.
|
|
49
|
+
</state_files>
|
|
50
|
+
|
|
51
|
+
<process>
|
|
52
|
+
|
|
53
|
+
## 1. Resolve the batch — canonical task ID list
|
|
54
|
+
|
|
55
|
+
The three input forms (`--tasks`, `--phases`, `--unblocked`) all resolve to a canonical `task_ids: [...]` list via the apt-tools helper:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
node packages/framework/bin/apt-tools.mjs fan-out resolve . --tasks T1,T2,T3,T4
|
|
59
|
+
# OR
|
|
60
|
+
node packages/framework/bin/apt-tools.mjs fan-out resolve . --phases m1/p1,m1/p2
|
|
61
|
+
# OR
|
|
62
|
+
node packages/framework/bin/apt-tools.mjs fan-out resolve . --unblocked
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"status": "ok",
|
|
69
|
+
"command": "fan-out-resolve",
|
|
70
|
+
"task_ids": ["T1", "T2", "T3", "T4"],
|
|
71
|
+
"source": "tasks",
|
|
72
|
+
"batch_id": "fanout-20260519-abc123"
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
If `--tasks` IDs fail the `/^[a-z0-9_-]+$/i` validation regex, the helper returns `status: error` and the skill body MUST abort.
|
|
77
|
+
|
|
78
|
+
## 2. Ensure each task has a worktree
|
|
79
|
+
|
|
80
|
+
For each `task_id` in the batch:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
node packages/framework/bin/apt-tools.mjs worktree create --task ${task_id}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
(Skip if `state.active_tasks[task_id].worktree_path` is already set — the create command is idempotent.)
|
|
87
|
+
|
|
88
|
+
## 3. Spawn N apt-executor subagents
|
|
89
|
+
|
|
90
|
+
Read the `parallelization.max_tasks` cap from `.aperant/config.json` (or default 4). Slice `task_ids` into batches of size `min(cap, task_ids.length)`. For each task in the current batch, dispatch via the host CLI's `Task` tool with `run_in_background: true`:
|
|
91
|
+
|
|
92
|
+
- `subagent: apt-executor`
|
|
93
|
+
- `prompt` includes:
|
|
94
|
+
- `batch_id` (from step 1)
|
|
95
|
+
- `task_id`
|
|
96
|
+
- The worktree path for this task
|
|
97
|
+
- A reminder: "Export `APT_AGENT_ID=fanout-${batch_id}-${task_id}` in every Bash invocation that runs `apt-tools` — see `packages/framework/agents/apt-executor.md` §0a."
|
|
98
|
+
|
|
99
|
+
The executor agent's `<process>` §0a already documents the env-export rule; the spawn prompt just has to seed `batch_id` and `task_id`.
|
|
100
|
+
|
|
101
|
+
## 4. Poll team-status until all workers reach a terminal state
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
node packages/framework/bin/apt-tools.mjs team list .
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Each worker's row uses its `APT_AGENT_ID=fanout-${batch_id}-${task_id}`. A worker is "done" when its corresponding task's `lifecycle_phase` is one of `closed`, `shipped-pending-merge`, or `blocked`.
|
|
108
|
+
|
|
109
|
+
## 5. Report
|
|
110
|
+
|
|
111
|
+
When all workers reach a terminal state, emit one consolidated summary: which tasks closed (verdict + PR URL), which are awaiting merge, which blocked. Recommend `/apt:close-task --all` for the post-merge sweep.
|
|
112
|
+
|
|
113
|
+
</process>
|
|
114
|
+
|
|
115
|
+
## Design references
|
|
116
|
+
|
|
117
|
+
- **ADR-0001** — Host CLI main thread is the conductor; no daemon, no in-skill queue.
|
|
118
|
+
- **ADR-0002** — One worktree per task; workers run independent feature branches.
|
|
119
|
+
- **ADR-0003** — Wave engine accepts `unit_kind: "task"` so cross-task fan-out reuses the same primitives as intra-task subtask waves.
|
|
120
|
+
|
|
121
|
+
## Config keys
|
|
122
|
+
|
|
123
|
+
- `parallelization.max_tasks` (default 4) — concurrency cap for the spawn loop.
|
|
124
|
+
- `parallelization.max_agents` (default 4) — global agent quota; the spawn loop respects whichever cap is lower.
|
|
@@ -1110,6 +1110,8 @@ For `AUTHORSHIP_MODE == "owned"`, continue normally.
|
|
|
1110
1110
|
|
|
1111
1111
|
AI agents have near-zero marginal cost per fix. The framework fixes **all** confirmed findings — critical, high, medium, and low — regardless of autonomy level. There is no severity gate and no "report for manual resolution" path. If a finding was confirmed by the validator, it gets fixed.
|
|
1112
1112
|
|
|
1113
|
+
**Hard rule — context budget is NOT a valid skip reason.** The orchestrator MAY NOT defer, drop, downgrade, or "punt for follow-up" any confirmed finding because its own context window feels heavy, because the run has already produced many fixes, or because it judges marginal value low. The query loop auto-compacts on its own — trust it. Per-finding fix work happens inside fix agents (separate context windows), so orchestrator pressure does not justify cutting their inputs. The only valid skip reasons are the ones enumerated in Key Principle #2 (`fixable: false`, critical finding without user approval, file assigned to another fixer). Any other skip is an unauthorized policy deviation — emit all confirmed findings to fixers, then let the audit gate adjudicate outcomes.
|
|
1114
|
+
|
|
1113
1115
|
| Autonomy | Action |
|
|
1114
1116
|
|----------|--------|
|
|
1115
1117
|
| `< 2` | Show findings summary before fixing. Then proceed with auto-fix. |
|
|
@@ -1647,7 +1649,7 @@ node packages/framework/bin/apt-tools.mjs workflow destroy . --id ${REVIEW_ID}
|
|
|
1647
1649
|
Agents do NOT assign confidence percentages. Either they have evidence (actual code snippet from Read tool) or they do not have a finding. The validator independently verifies every finding against real code.
|
|
1648
1650
|
|
|
1649
1651
|
### 2. Fix Everything Found
|
|
1650
|
-
If it was found and confirmed, fix it. ALL severities. "This is minor" is NOT a valid skip reason. The only valid skip reasons are: `fixable: false`, critical findings without user approval, or files assigned to another fixer.
|
|
1652
|
+
If it was found and confirmed, fix it. ALL severities. "This is minor" is NOT a valid skip reason. **Neither is "my context window is heavy" or "I've already done a lot this run"** — orchestrator context pressure never authorizes dropping a confirmed finding (see Phase 6 → Principle: Fix Everything). The only valid skip reasons are: `fixable: false`, critical findings without user approval, or files assigned to another fixer.
|
|
1651
1653
|
|
|
1652
1654
|
### 3. Findings Flow Through Files
|
|
1653
1655
|
Agents write to `.aperant/pr-reviews/{review-id}/findings/`. The orchestrator reads with the Read tool. Fix agents read finding files directly. This keeps the orchestrator context under 30%.
|
|
@@ -1711,6 +1713,11 @@ Fix agents self-report their outcome via a status line (`FIXED: X | FAILED: Y |
|
|
|
1711
1713
|
**Wrong:** Skipping Phase 6 entirely when autonomy < 2.
|
|
1712
1714
|
**Right:** Always fix all confirmed findings. At autonomy < 2, show findings summary first, then proceed with auto-fix. The only difference is whether findings are displayed before fixing — not whether they get fixed.
|
|
1713
1715
|
|
|
1716
|
+
### Mistake 7a: Dropping Findings Because Orchestrator Context Feels Heavy
|
|
1717
|
+
|
|
1718
|
+
**Wrong:** Mid-Phase-6, the orchestrator decides "my window is getting heavy after 6 specialist rounds + Round 1 fixes — I'll skip the minor findings and report a partial fix." This is an unauthorized policy deviation, even when phrased as "judgment call" or "context-budget reasons."
|
|
1719
|
+
**Right:** Spawn fix agents for **every** confirmed finding regardless of orchestrator context state. Fix work happens in the fixer's window, not yours. The query loop auto-compacts when it needs to — trust it. If you genuinely cannot fit the spawn calls, that is a framework bug to file (not a license to ship a partial fix); emit all findings to fixers in the smallest batches possible and let the audit gate adjudicate. Severity, "diminishing returns," and your own token pressure are NOT skip authorities — only the three reasons in Key Principle #2 are.
|
|
1720
|
+
|
|
1714
1721
|
### Mistake 8: Forgetting to Update metadata.json
|
|
1715
1722
|
|
|
1716
1723
|
**Wrong:** Writing review.json but leaving metadata.json stuck at `"status": "in-progress"`.
|
|
@@ -333,6 +333,10 @@ Scores: logic={n} security={n} quality={n} architecture={n} performance={n}
|
|
|
333
333
|
Report: {task_dir}/review.json
|
|
334
334
|
```
|
|
335
335
|
|
|
336
|
+
`lifecycle_phase` remains at `reviewing` after /apt:review completes — this is
|
|
337
|
+
the from-state /apt:ship expects for its atomic `reviewing → shipped-pending-merge`
|
|
338
|
+
flip, so no further `task update --lifecycle-phase` call is needed here.
|
|
339
|
+
|
|
336
340
|
**Always end with a "What's Next" recommendation:**
|
|
337
341
|
|
|
338
342
|
```
|