@aperant/framework 0.6.4 → 0.6.6
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 +38 -0
- package/README.md +54 -234
- package/agents/apt-improver.md +99 -0
- package/agents/apt-planner.md +115 -10
- package/dist/__test-helpers/run-cmd.d.mts +4 -2
- package/dist/__test-helpers/run-cmd.d.mts.map +1 -1
- package/dist/__test-helpers/run-cmd.mjs +56 -15
- package/dist/__test-helpers/run-cmd.mjs.map +1 -1
- package/dist/cli/ci-watch/lock.d.mts +53 -0
- package/dist/cli/ci-watch/lock.d.mts.map +1 -0
- package/dist/cli/ci-watch/lock.mjs +172 -0
- package/dist/cli/ci-watch/lock.mjs.map +1 -0
- package/dist/cli/ci-watch/state.d.mts +36 -0
- package/dist/cli/ci-watch/state.d.mts.map +1 -0
- package/dist/cli/ci-watch/state.mjs +103 -0
- package/dist/cli/ci-watch/state.mjs.map +1 -0
- package/dist/cli/ci-watch/stop-matrix.d.mts +58 -0
- package/dist/cli/ci-watch/stop-matrix.d.mts.map +1 -0
- package/dist/cli/ci-watch/stop-matrix.mjs +164 -0
- package/dist/cli/ci-watch/stop-matrix.mjs.map +1 -0
- package/dist/cli/cli-wrappers/ci-watch.d.mts +2 -0
- package/dist/cli/cli-wrappers/ci-watch.d.mts.map +1 -0
- package/dist/cli/cli-wrappers/ci-watch.mjs +9 -0
- package/dist/cli/cli-wrappers/ci-watch.mjs.map +1 -0
- package/dist/cli/commands/adr.d.mts +5 -0
- package/dist/cli/commands/adr.d.mts.map +1 -0
- package/dist/cli/commands/adr.mjs +228 -0
- package/dist/cli/commands/adr.mjs.map +1 -0
- package/dist/cli/commands/ci-watch.d.mts +7 -0
- package/dist/cli/commands/ci-watch.d.mts.map +1 -0
- package/dist/cli/commands/ci-watch.mjs +465 -0
- package/dist/cli/commands/ci-watch.mjs.map +1 -0
- package/dist/cli/commands/commit.d.mts.map +1 -1
- package/dist/cli/commands/commit.mjs +25 -8
- package/dist/cli/commands/commit.mjs.map +1 -1
- package/dist/cli/commands/context.d.mts +7 -0
- package/dist/cli/commands/context.d.mts.map +1 -0
- package/dist/cli/commands/context.mjs +224 -0
- package/dist/cli/commands/context.mjs.map +1 -0
- package/dist/cli/commands/event.d.mts.map +1 -1
- package/dist/cli/commands/event.mjs +125 -24
- package/dist/cli/commands/event.mjs.map +1 -1
- package/dist/cli/commands/host-detect.d.mts +1 -1
- package/dist/cli/commands/host-detect.d.mts.map +1 -1
- package/dist/cli/commands/host-detect.mjs +30 -3
- package/dist/cli/commands/host-detect.mjs.map +1 -1
- package/dist/cli/commands/modes.d.mts +13 -0
- package/dist/cli/commands/modes.d.mts.map +1 -0
- package/dist/cli/commands/modes.mjs +221 -0
- package/dist/cli/commands/modes.mjs.map +1 -0
- package/dist/cli/commands/route.mjs +1 -1
- package/dist/cli/commands/route.mjs.map +1 -1
- package/dist/cli/commands/task.d.mts.map +1 -1
- package/dist/cli/commands/task.mjs +393 -145
- package/dist/cli/commands/task.mjs.map +1 -1
- package/dist/cli/commands/tokens.d.mts.map +1 -1
- package/dist/cli/commands/tokens.mjs +150 -6
- package/dist/cli/commands/tokens.mjs.map +1 -1
- package/dist/cli/commands/triage.d.mts +8 -0
- package/dist/cli/commands/triage.d.mts.map +1 -0
- package/dist/cli/commands/triage.mjs +263 -0
- package/dist/cli/commands/triage.mjs.map +1 -0
- package/dist/cli/config/post-merge-sweep.d.mts +18 -0
- package/dist/cli/config/post-merge-sweep.d.mts.map +1 -1
- package/dist/cli/config/post-merge-sweep.mjs +48 -3
- package/dist/cli/config/post-merge-sweep.mjs.map +1 -1
- package/dist/cli/consistency/registry.d.mts +11 -0
- package/dist/cli/consistency/registry.d.mts.map +1 -1
- package/dist/cli/consistency/registry.mjs +13 -0
- package/dist/cli/consistency/registry.mjs.map +1 -1
- package/dist/cli/coordination/agent-identity.d.mts +9 -0
- package/dist/cli/coordination/agent-identity.d.mts.map +1 -1
- package/dist/cli/coordination/agent-identity.mjs +11 -0
- package/dist/cli/coordination/agent-identity.mjs.map +1 -1
- package/dist/cli/coordination/auto-emit-artifact-ready.d.mts +16 -0
- package/dist/cli/coordination/auto-emit-artifact-ready.d.mts.map +1 -0
- package/dist/cli/coordination/auto-emit-artifact-ready.mjs +88 -0
- package/dist/cli/coordination/auto-emit-artifact-ready.mjs.map +1 -0
- package/dist/cli/coordination/event-log.d.mts +27 -0
- package/dist/cli/coordination/event-log.d.mts.map +1 -1
- package/dist/cli/coordination/event-log.mjs +72 -2
- package/dist/cli/coordination/event-log.mjs.map +1 -1
- package/dist/cli/coordination/event-schema.d.mts +16 -0
- package/dist/cli/coordination/event-schema.d.mts.map +1 -0
- package/dist/cli/coordination/event-schema.mjs +161 -0
- package/dist/cli/coordination/event-schema.mjs.map +1 -0
- package/dist/cli/coordination/store.d.mts +6 -0
- package/dist/cli/coordination/store.d.mts.map +1 -1
- package/dist/cli/coordination/store.mjs +14 -0
- package/dist/cli/coordination/store.mjs.map +1 -1
- package/dist/cli/dispatch.d.mts.map +1 -1
- package/dist/cli/dispatch.mjs +14 -1
- package/dist/cli/dispatch.mjs.map +1 -1
- package/dist/cli/gate/registry.d.mts +11 -0
- package/dist/cli/gate/registry.d.mts.map +1 -1
- package/dist/cli/gate/registry.mjs +13 -0
- package/dist/cli/gate/registry.mjs.map +1 -1
- package/dist/cli/help.d.mts.map +1 -1
- package/dist/cli/help.mjs +3 -2
- package/dist/cli/help.mjs.map +1 -1
- package/dist/cli/host/detect.d.mts +1 -0
- package/dist/cli/host/detect.d.mts.map +1 -1
- package/dist/cli/host/detect.mjs +5 -0
- package/dist/cli/host/detect.mjs.map +1 -1
- package/dist/cli/roadmap/conductor-view.d.mts +13 -0
- package/dist/cli/roadmap/conductor-view.d.mts.map +1 -0
- package/dist/cli/roadmap/conductor-view.mjs +31 -0
- package/dist/cli/roadmap/conductor-view.mjs.map +1 -0
- package/dist/cli/route/envelope.d.mts +68 -4
- package/dist/cli/route/envelope.d.mts.map +1 -1
- package/dist/cli/route/envelope.mjs +140 -103
- package/dist/cli/route/envelope.mjs.map +1 -1
- package/dist/cli/route/skill-discover.d.mts +9 -0
- package/dist/cli/route/skill-discover.d.mts.map +1 -1
- package/dist/cli/route/skill-discover.mjs +13 -1
- package/dist/cli/route/skill-discover.mjs.map +1 -1
- package/dist/cli/skill-author/contract.d.mts +1 -0
- package/dist/cli/skill-author/contract.d.mts.map +1 -1
- package/dist/cli/skill-author/contract.mjs +7 -0
- package/dist/cli/skill-author/contract.mjs.map +1 -1
- package/dist/cli/task/ids.d.mts +7 -4
- package/dist/cli/task/ids.d.mts.map +1 -1
- package/dist/cli/task/ids.mjs +11 -10
- package/dist/cli/task/ids.mjs.map +1 -1
- package/dist/cli/task/worktree-cleanup.d.mts +9 -1
- package/dist/cli/task/worktree-cleanup.d.mts.map +1 -1
- package/dist/cli/task/worktree-cleanup.mjs +190 -9
- package/dist/cli/task/worktree-cleanup.mjs.map +1 -1
- package/dist/plugin/.claude-plugin/plugin.json +1 -1
- package/dist/plugin/agents/apt-planner.md +1 -1
- package/dist/plugin/skills/apt/SKILL.md +111 -5
- package/dist/plugin/skills/apt-author-skill/SKILL.md +11 -0
- package/dist/plugin/skills/apt-bootstrap/SKILL.md +1 -0
- package/dist/plugin/skills/apt-classify/SKILL.md +1 -0
- package/dist/plugin/skills/apt-close-task/SKILL.md +1 -0
- package/dist/plugin/skills/apt-create-docs/SKILL.md +1 -0
- package/dist/plugin/skills/apt-debug/SKILL.md +2 -0
- package/dist/plugin/skills/apt-design/SKILL.md +2 -0
- package/dist/plugin/skills/apt-discuss/SKILL.md +2 -0
- package/dist/plugin/skills/apt-docs/SKILL.md +2 -0
- package/dist/plugin/skills/apt-execute/SKILL.md +1 -0
- package/dist/plugin/skills/apt-mockup/SKILL.md +2 -0
- package/dist/plugin/skills/apt-pause/SKILL.md +1 -0
- package/dist/plugin/skills/apt-personas/SKILL.md +1 -0
- package/dist/plugin/skills/apt-plan/SKILL.md +2 -0
- package/dist/plugin/skills/apt-pr-review/SKILL.md +1 -0
- package/dist/plugin/skills/apt-quick/SKILL.md +2 -0
- package/dist/plugin/skills/apt-resume/SKILL.md +1 -0
- package/dist/plugin/skills/apt-review/SKILL.md +1 -0
- package/dist/plugin/skills/apt-roadmap/SKILL.md +1 -0
- package/dist/plugin/skills/apt-roundtable/SKILL.md +2 -0
- package/dist/plugin/skills/apt-run/SKILL.md +1 -0
- package/dist/plugin/skills/apt-scan/SKILL.md +1 -0
- package/dist/plugin/skills/apt-setup/SKILL.md +1 -0
- package/dist/plugin/skills/apt-ship/SKILL.md +6 -5
- package/dist/plugin/skills/apt-stress-test/SKILL.md +1 -0
- package/dist/plugin/skills/apt-terminal/SKILL.md +1 -0
- package/dist/plugin/skills/apt-update/SKILL.md +5 -0
- package/dist/plugin/skills/apt-verify/SKILL.md +1 -0
- package/dist/plugin/skills/apt-verify-proof/SKILL.md +1 -0
- package/dist/types/config.d.ts +85 -0
- package/dist/types/config.d.ts.map +1 -1
- package/package.json +9 -1
- package/prompts/coder.md +2 -0
- package/prompts/conductor-status-check.md +23 -0
- package/prompts/conductor-sub-agent.md +57 -0
- package/prompts/conductor-system.md +172 -0
- package/prompts/planner.md +12 -0
- package/prompts/spec_writer.md +9 -0
- package/skills/apt/SKILL.md +1 -0
- package/skills/apt-close-task/SKILL.md +32 -1
- package/skills/apt-debug/SKILL.md +39 -6
- package/skills/apt-debug/appendices/diagnose-discipline.md +119 -0
- package/skills/apt-diagram/SKILL.md +342 -0
- package/skills/apt-diagram/appendices/design-discipline.md +97 -0
- package/skills/apt-discuss/SKILL.md +25 -0
- package/skills/apt-discuss/appendices/grill-discipline.md +104 -0
- package/skills/apt-discuss/appendices/zoom-out-helper.md +79 -0
- package/skills/apt-execute/SKILL.md +48 -5
- package/skills/apt-execute/appendices/tdd-mode.md +107 -0
- package/skills/apt-improve/DEEPENING.md +84 -0
- package/skills/apt-improve/INTERFACE-DESIGN.md +97 -0
- package/skills/apt-improve/LANGUAGE.md +104 -0
- package/skills/apt-improve/SKILL.md +141 -0
- package/skills/apt-plan/SKILL.md +159 -4
- package/skills/apt-plan/adapters/conductor.md +98 -0
- package/skills/apt-planner.md +42 -1
- package/skills/apt-pr-review/SKILL.md +46 -16
- package/skills/apt-prototype/LOGIC.md +109 -0
- package/skills/apt-prototype/SKILL.md +143 -0
- package/skills/apt-prototype/UI.md +90 -0
- package/skills/apt-quick/SKILL.md +30 -0
- package/skills/apt-review/SKILL.md +2 -0
- package/skills/apt-run/SKILL.md +32 -4
- package/skills/apt-setup/SKILL.md +130 -2
- package/skills/apt-ship/SKILL.md +63 -1
- package/skills/apt-spar/SKILL.md +290 -0
- package/skills/apt-triage/AGENT-BRIEF.md +84 -0
- package/skills/apt-triage/OUT-OF-SCOPE.md +75 -0
- package/skills/apt-triage/SKILL.md +169 -0
- package/skills/apt-verify/SKILL.md +3 -0
- package/skills/apt-verify-proof/SKILL.md +3 -0
- package/skills/apt-watch-ci/SKILL.md +163 -0
- package/skills/apt-zoom-out/SKILL.md +130 -0
- package/src/cli/ci-watch/lock.mjs +178 -0
- package/src/cli/ci-watch/state.mjs +103 -0
- package/src/cli/ci-watch/stop-matrix.mjs +181 -0
- package/src/cli/cli-wrappers/ci-watch.mjs +9 -0
- package/src/cli/commands/adr.mjs +243 -0
- package/src/cli/commands/ci-watch.mjs +503 -0
- package/src/cli/commands/commit.mjs +27 -8
- package/src/cli/commands/context.mjs +244 -0
- package/src/cli/commands/event.mjs +131 -24
- package/src/cli/commands/host-detect.mjs +33 -7
- package/src/cli/commands/modes.mjs +216 -0
- package/src/cli/commands/route.mjs +1 -1
- package/src/cli/commands/task.mjs +430 -170
- package/src/cli/commands/tokens.mjs +157 -6
- package/src/cli/commands/triage.mjs +286 -0
- package/src/cli/config/post-merge-sweep.mjs +49 -3
- package/src/cli/consistency/registry.mjs +14 -0
- package/src/cli/coordination/agent-identity.mjs +12 -0
- package/src/cli/coordination/auto-emit-artifact-ready.mjs +96 -0
- package/src/cli/coordination/event-log.mjs +73 -2
- package/src/cli/coordination/event-schema.d.ts +13 -0
- package/src/cli/coordination/event-schema.mjs +174 -0
- package/src/cli/coordination/store.mjs +14 -0
- package/src/cli/dispatch.mjs +15 -1
- package/src/cli/gate/registry.mjs +14 -0
- package/src/cli/help.mjs +3 -2
- package/src/cli/host/detect.mjs +5 -0
- package/src/cli/roadmap/conductor-view.d.ts +10 -0
- package/src/cli/roadmap/conductor-view.mjs +31 -0
- package/src/cli/route/envelope.mjs +140 -106
- package/src/cli/route/skill-discover.mjs +14 -1
- package/src/cli/skill-author/contract.mjs +7 -0
- package/src/cli/task/ids.mjs +15 -13
- package/src/cli/task/worktree-cleanup.mjs +191 -9
- package/templates/adr-format.md +56 -0
- package/templates/config.json +4 -0
- package/templates/context-format.md +34 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @internal
|
|
3
|
+
* auto-emit-artifact-ready.mjs — code-enforced plan-ready signal.
|
|
4
|
+
*
|
|
5
|
+
* Promotes the `artifact.ready{kind:'plan'}` emit from a markdown contract
|
|
6
|
+
* in `skills/apt-plan/adapters/conductor.md` to a framework-level
|
|
7
|
+
* postcondition of `apt-tools commit`. This closes the Conductor v2 gap
|
|
8
|
+
* exposed during live verification 2026-05-15: the planner's QUICK /
|
|
9
|
+
* SIMPLE track is hard-exempt from "new interaction discipline"
|
|
10
|
+
* (Pocock Fast Path Guarantee ID-05), so the markdown emit step was
|
|
11
|
+
* never executed for tasks router-classified as simple. The fleet
|
|
12
|
+
* never saw artifact.ready, the PlanReadyCard never auto-promoted.
|
|
13
|
+
*
|
|
14
|
+
* The fix lives in `framework` rather than the skill so:
|
|
15
|
+
* - it works for QUICK + STANDARD + DEEP + COMPLEX uniformly
|
|
16
|
+
* - it doesn't violate the Fast Path Guarantee (a passive log emit
|
|
17
|
+
* is not interaction discipline)
|
|
18
|
+
* - it's the same code path every CLI consumer (Claude Code, Gemini,
|
|
19
|
+
* OpenCode, Codex) traverses via `apt-tools commit`
|
|
20
|
+
*
|
|
21
|
+
* Conditions for emit (ALL must hold — otherwise no-op):
|
|
22
|
+
* 1. `APERANT_TERMINAL_ID` is set in process.env (Conductor PTY context).
|
|
23
|
+
* 2. The committed files include a path matching
|
|
24
|
+
* `.aperant/tasks/{task-id}/implementation_plan.json`.
|
|
25
|
+
*
|
|
26
|
+
* The emit re-uses the same `event-schema` validator and JSONL writer
|
|
27
|
+
* `commands/event.mjs` already uses, so the envelope structure stays
|
|
28
|
+
* byte-identical with manually-emitted ones.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { createHash } from 'node:crypto'
|
|
32
|
+
import { appendFileSync, mkdirSync, readFileSync } from 'node:fs'
|
|
33
|
+
import { hostname } from 'node:os'
|
|
34
|
+
import { join } from 'node:path'
|
|
35
|
+
import { validateConductorEvent } from './event-schema.mjs'
|
|
36
|
+
|
|
37
|
+
const PLAN_PATH_RE = /^(?:\.\/)?\.aperant\/tasks\/([^/]+)\/implementation_plan\.json$/
|
|
38
|
+
|
|
39
|
+
let _incarnationCounter = 0
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @param {{ files: string[], projectRoot: string }} input
|
|
43
|
+
* @returns {{ emitted: false, reason: string } | { emitted: true, envelope: object, file: string }}
|
|
44
|
+
*/
|
|
45
|
+
export function maybeAutoEmitPlanReady({ files, projectRoot }) {
|
|
46
|
+
const terminalId = process.env.APERANT_TERMINAL_ID
|
|
47
|
+
if (!terminalId) return { emitted: false, reason: 'APERANT_TERMINAL_ID unset' }
|
|
48
|
+
|
|
49
|
+
const match = files.map((f) => f.replace(/\\/g, '/').match(PLAN_PATH_RE)).find((m) => m !== null)
|
|
50
|
+
if (!match) return { emitted: false, reason: 'no implementation_plan.json in committed files' }
|
|
51
|
+
const taskId = match[1]
|
|
52
|
+
|
|
53
|
+
const planRelPath = `.aperant/tasks/${taskId}/implementation_plan.json`
|
|
54
|
+
const absolutePath = join(projectRoot, planRelPath)
|
|
55
|
+
|
|
56
|
+
let hash
|
|
57
|
+
try {
|
|
58
|
+
const body = readFileSync(absolutePath)
|
|
59
|
+
hash = createHash('sha256').update(body).digest('hex')
|
|
60
|
+
} catch (err) {
|
|
61
|
+
return { emitted: false, reason: `plan file unreadable: ${(err && err.message) || err}` }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const data = {
|
|
65
|
+
kind: 'plan',
|
|
66
|
+
path: planRelPath,
|
|
67
|
+
hash,
|
|
68
|
+
request_id: `plan-${taskId}-${Date.now()}`,
|
|
69
|
+
task_id: taskId,
|
|
70
|
+
terminal_id: terminalId,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const validation = validateConductorEvent('artifact.ready', data)
|
|
74
|
+
if (!validation.ok) {
|
|
75
|
+
return { emitted: false, reason: `schema violation: ${validation.error}` }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const envelope = {
|
|
79
|
+
t: new Date().toISOString(),
|
|
80
|
+
agent: `${hostname()}-${process.pid}`,
|
|
81
|
+
pid: process.pid,
|
|
82
|
+
hostname: hostname(),
|
|
83
|
+
incarnation: ++_incarnationCounter,
|
|
84
|
+
op: 'artifact.ready',
|
|
85
|
+
task: taskId,
|
|
86
|
+
data,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const eventsDir = join(projectRoot, '.aperant', 'events')
|
|
90
|
+
mkdirSync(eventsDir, { recursive: true })
|
|
91
|
+
const dayFile = `${new Date().toISOString().slice(0, 10)}.jsonl`
|
|
92
|
+
const fullPath = join(eventsDir, dayFile)
|
|
93
|
+
appendFileSync(fullPath, `${JSON.stringify(envelope)}\n`, 'utf-8')
|
|
94
|
+
|
|
95
|
+
return { emitted: true, envelope, file: dayFile }
|
|
96
|
+
}
|
|
@@ -8,8 +8,19 @@
|
|
|
8
8
|
* multiple processes running `task update` / `task close` cannot tear each
|
|
9
9
|
* other's lines.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
* seam (store.appendEvent).
|
|
11
|
+
* Consumption policy:
|
|
12
|
+
* - WRITES go through the CoordinationStore seam (`store.appendEvent`).
|
|
13
|
+
* This is the policy boundary — concurrent writers must coordinate.
|
|
14
|
+
* - READ-ONLY helpers (`readLifecycleEventsForDay`,
|
|
15
|
+
* `findMostRecentEventForTask`, `readTaskEventsOverDays`) MAY be
|
|
16
|
+
* imported directly. They do not mutate state and the event log is
|
|
17
|
+
* append-only, so a stale read is the worst case and is bounded by
|
|
18
|
+
* "the next read picks up new appends." Direct read imports keep the
|
|
19
|
+
* R6 telemetry surface (PR #115) out of the store seam, which is
|
|
20
|
+
* scoped to coordination primitives.
|
|
21
|
+
*
|
|
22
|
+
* Do NOT import this module from bin/apt-tools.mjs directly. CLI commands
|
|
23
|
+
* import the helpers above via their command modules.
|
|
13
24
|
*
|
|
14
25
|
* The existing numbered-JSON event surface in commands/event.mjs is NOT
|
|
15
26
|
* touched — its `rebuild-state` consumer keeps working. The JSONL log is
|
|
@@ -86,3 +97,63 @@ export function readLifecycleEventsForDay(projectRoot, day = todayBucket()) {
|
|
|
86
97
|
}
|
|
87
98
|
return out
|
|
88
99
|
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Read events for a task across the today + yesterday UTC day buckets,
|
|
103
|
+
* sorted ascending by `t`. Optionally filtered to events whose `op` starts
|
|
104
|
+
* with `opPrefix`. Covers tasks that span a UTC day boundary without
|
|
105
|
+
* globbing the whole `.aperant/events/` directory.
|
|
106
|
+
*
|
|
107
|
+
* Read-only — does NOT mutate state. Per the original `@internal` policy
|
|
108
|
+
* this module is reached via `store.appendEvent` for WRITES, but read-only
|
|
109
|
+
* helpers like this one (and `readLifecycleEventsForDay`) are direct
|
|
110
|
+
* imports because there is no state to coordinate (PR #115 review finding
|
|
111
|
+
* FIT-001 — comment updated accordingly).
|
|
112
|
+
*
|
|
113
|
+
* @internal
|
|
114
|
+
*/
|
|
115
|
+
export function readTaskEventsOverDays(projectRoot, taskId, opPrefix = null) {
|
|
116
|
+
function filterAndPush(events, into) {
|
|
117
|
+
for (const e of events) {
|
|
118
|
+
if (e.task !== taskId) continue
|
|
119
|
+
if (opPrefix && e.op !== opPrefix && !e.op.startsWith(opPrefix)) continue
|
|
120
|
+
into.push(e)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const today = todayBucket()
|
|
124
|
+
const yesterday = new Date()
|
|
125
|
+
yesterday.setUTCDate(yesterday.getUTCDate() - 1)
|
|
126
|
+
const yest = yesterday.toISOString().slice(0, 10)
|
|
127
|
+
const out = []
|
|
128
|
+
// Yesterday first so the combined array stays chronological.
|
|
129
|
+
filterAndPush(readLifecycleEventsForDay(projectRoot, yest), out)
|
|
130
|
+
filterAndPush(readLifecycleEventsForDay(projectRoot, today), out)
|
|
131
|
+
return out
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Find the most recent event matching `taskId`, optionally filtered to events
|
|
136
|
+
* whose `op` starts with `opPrefix`. Looks at today's bucket first; only
|
|
137
|
+
* reads yesterday's bucket on a today-miss — keeps the common case to a
|
|
138
|
+
* single file read.
|
|
139
|
+
*
|
|
140
|
+
* Used by R6 telemetry to compute `elapsed_ms` between lifecycle transitions
|
|
141
|
+
* (roundtable 2026-05-14). Returns null when no matching event is found.
|
|
142
|
+
*
|
|
143
|
+
* @internal
|
|
144
|
+
*/
|
|
145
|
+
export function findMostRecentEventForTask(projectRoot, taskId, opPrefix = null) {
|
|
146
|
+
function search(events) {
|
|
147
|
+
const filtered = events.filter(
|
|
148
|
+
(e) => e.task === taskId && (!opPrefix || e.op === opPrefix || e.op.startsWith(opPrefix)),
|
|
149
|
+
)
|
|
150
|
+
return filtered.length > 0 ? filtered[filtered.length - 1] : null
|
|
151
|
+
}
|
|
152
|
+
const today = todayBucket()
|
|
153
|
+
const todayMatch = search(readLifecycleEventsForDay(projectRoot, today))
|
|
154
|
+
if (todayMatch) return todayMatch
|
|
155
|
+
const yesterday = new Date()
|
|
156
|
+
yesterday.setUTCDate(yesterday.getUTCDate() - 1)
|
|
157
|
+
const yest = yesterday.toISOString().slice(0, 10)
|
|
158
|
+
return search(readLifecycleEventsForDay(projectRoot, yest))
|
|
159
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type declarations for the .mjs validator. Hand-authored because the
|
|
3
|
+
* import path points at src/.mjs directly (no dist roundtrip).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type ValidateResult = { ok: true } | { ok: false; error: string }
|
|
7
|
+
|
|
8
|
+
export function validateConductorEvent(
|
|
9
|
+
op: string,
|
|
10
|
+
data: Record<string, unknown> | undefined | null,
|
|
11
|
+
): ValidateResult
|
|
12
|
+
|
|
13
|
+
export const CONDUCTOR_VALIDATED_OPS: ReadonlySet<string>
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @internal
|
|
3
|
+
* event-schema.mjs — single source of truth for conductor event payload shapes.
|
|
4
|
+
*
|
|
5
|
+
* Why this lives in `framework`, not `core`:
|
|
6
|
+
* `packages/core` depends on `@aperant/framework`. The schema is shared by
|
|
7
|
+
* the CLI write path (`commands/event.mjs`), the framework lifecycle write
|
|
8
|
+
* seam (`coordination/store.mjs:appendEvent`), and the core IPC write seam
|
|
9
|
+
* (`packages/core/src/runtime/orchestration/conductor/event-log.ts`).
|
|
10
|
+
* Putting it in the lower layer keeps a single rule table that every
|
|
11
|
+
* writer can import.
|
|
12
|
+
*
|
|
13
|
+
* Shape: zero-dep plain JS validator returning a `{ ok, error? }` result.
|
|
14
|
+
* We deliberately avoid zod here so `.mjs` callers (event.mjs, store.mjs)
|
|
15
|
+
* don't need a build step, and so the core TS writer can use the same
|
|
16
|
+
* identity-shaped result without translating between zod and a custom
|
|
17
|
+
* error type.
|
|
18
|
+
*
|
|
19
|
+
* Per Codex roundtable lock 2026-05-15:
|
|
20
|
+
* - `phase.changed` is reserved for PTY busy/idle transitions ONLY.
|
|
21
|
+
* Plan completion uses `artifact.ready{kind:'plan'}`, not phase.changed.
|
|
22
|
+
* - `awaiting-input` / `input-rescinded` MUST carry both terminal_id and
|
|
23
|
+
* request_id so the drawer can pair cards strictly.
|
|
24
|
+
* - `artifact.ready{kind:'plan'}` is the canonical plan-completion signal
|
|
25
|
+
* consumed by the conductor's reviewer queue (Step 5's reviewPlan tool).
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/** Conductor ops the schema enforces. Other ops pass through unchanged. */
|
|
29
|
+
const CONDUCTOR_OPS = new Set([
|
|
30
|
+
'phase.changed',
|
|
31
|
+
'artifact.ready',
|
|
32
|
+
'awaiting-input',
|
|
33
|
+
'input-rescinded',
|
|
34
|
+
'heartbeat',
|
|
35
|
+
'budget.exceeded',
|
|
36
|
+
'terminal.enrolled',
|
|
37
|
+
'terminal.unenrolled',
|
|
38
|
+
'conductor.intent',
|
|
39
|
+
'conductor.cross_check',
|
|
40
|
+
'conductor.realignment',
|
|
41
|
+
// c-4 (action-capable) — gate verdict + boot-replay audit trail
|
|
42
|
+
'conductor.action.executed',
|
|
43
|
+
'conductor.action.gate_blocked',
|
|
44
|
+
'conductor.boot_reconciled',
|
|
45
|
+
])
|
|
46
|
+
|
|
47
|
+
/** Allowed values for `phase.changed.to` — PTY busy/idle only. */
|
|
48
|
+
const ALLOWED_PHASE_VALUES = new Set(['busy', 'idle'])
|
|
49
|
+
|
|
50
|
+
/** Allowed values for `artifact.ready.kind`. */
|
|
51
|
+
const ALLOWED_ARTIFACT_KINDS = new Set(['plan', 'spec', 'build', 'review'])
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Validate the data payload of a conductor event against the op's contract.
|
|
55
|
+
*
|
|
56
|
+
* @param {string} op
|
|
57
|
+
* @param {Record<string, unknown> | undefined | null} data
|
|
58
|
+
* @returns {{ ok: true } | { ok: false, error: string }}
|
|
59
|
+
*/
|
|
60
|
+
export function validateConductorEvent(op, data) {
|
|
61
|
+
if (!CONDUCTOR_OPS.has(op)) return { ok: true }
|
|
62
|
+
const d = data && typeof data === 'object' ? data : {}
|
|
63
|
+
|
|
64
|
+
switch (op) {
|
|
65
|
+
case 'awaiting-input':
|
|
66
|
+
case 'input-rescinded':
|
|
67
|
+
return requireFields(op, d, ['terminal_id', 'request_id'])
|
|
68
|
+
|
|
69
|
+
case 'phase.changed': {
|
|
70
|
+
const r = requireFields(op, d, ['from', 'to', 'terminal_id', 'request_id'])
|
|
71
|
+
if (!r.ok) return r
|
|
72
|
+
if (!ALLOWED_PHASE_VALUES.has(String(d.to))) {
|
|
73
|
+
return {
|
|
74
|
+
ok: false,
|
|
75
|
+
error: `phase.changed.to must be one of [${[...ALLOWED_PHASE_VALUES].join(', ')}], got: ${String(
|
|
76
|
+
d.to,
|
|
77
|
+
)}. Plan completion must use artifact.ready{kind:'plan'}.`,
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return { ok: true }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
case 'artifact.ready': {
|
|
84
|
+
const r = requireFields(op, d, [
|
|
85
|
+
'kind',
|
|
86
|
+
'path',
|
|
87
|
+
'hash',
|
|
88
|
+
'request_id',
|
|
89
|
+
'terminal_id',
|
|
90
|
+
'task_id',
|
|
91
|
+
])
|
|
92
|
+
if (!r.ok) return r
|
|
93
|
+
if (!ALLOWED_ARTIFACT_KINDS.has(String(d.kind))) {
|
|
94
|
+
return {
|
|
95
|
+
ok: false,
|
|
96
|
+
error: `artifact.ready.kind must be one of [${[...ALLOWED_ARTIFACT_KINDS].join(
|
|
97
|
+
', ',
|
|
98
|
+
)}], got: ${String(d.kind)}`,
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return { ok: true }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
case 'conductor.intent':
|
|
105
|
+
return requireFields(op, d, ['terminal_id', 'request_id', 'action', 'reasoning'])
|
|
106
|
+
|
|
107
|
+
case 'conductor.cross_check':
|
|
108
|
+
return requireFields(op, d, ['terminal_id', 'request_id'])
|
|
109
|
+
|
|
110
|
+
case 'conductor.realignment': {
|
|
111
|
+
const r = requireFields(op, d, ['terminal_id', 'request_id_orig'])
|
|
112
|
+
if (!r.ok) return r
|
|
113
|
+
if (d.attempt_n === undefined || d.attempt_n === null)
|
|
114
|
+
return { ok: false, error: `${op} missing required field(s): attempt_n` }
|
|
115
|
+
return { ok: true }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
case 'terminal.enrolled':
|
|
119
|
+
case 'terminal.unenrolled':
|
|
120
|
+
case 'heartbeat':
|
|
121
|
+
case 'budget.exceeded':
|
|
122
|
+
return requireFields(op, d, ['terminal_id'])
|
|
123
|
+
|
|
124
|
+
case 'conductor.action.executed':
|
|
125
|
+
return requireFields(op, d, ['terminal_id', 'request_id', 'action_kind', 'idempotency_key'])
|
|
126
|
+
|
|
127
|
+
case 'conductor.action.gate_blocked':
|
|
128
|
+
return requireFields(op, d, [
|
|
129
|
+
'terminal_id',
|
|
130
|
+
'request_id',
|
|
131
|
+
'action_kind',
|
|
132
|
+
'idempotency_key',
|
|
133
|
+
'blocked_reason',
|
|
134
|
+
])
|
|
135
|
+
|
|
136
|
+
case 'conductor.boot_reconciled': {
|
|
137
|
+
// Fleet-wide event — numeric fields only. All optional but if
|
|
138
|
+
// present must be finite numbers (zero allowed).
|
|
139
|
+
const numFields = ['cleaned_leases', 'replayed', 'start_offset']
|
|
140
|
+
for (const f of numFields) {
|
|
141
|
+
if (d[f] === undefined) continue
|
|
142
|
+
if (typeof d[f] !== 'number' || !Number.isFinite(d[f])) {
|
|
143
|
+
return {
|
|
144
|
+
ok: false,
|
|
145
|
+
error: `${op} field ${f} must be a finite number, got: ${String(d[f])}`,
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return { ok: true }
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
default:
|
|
153
|
+
return { ok: true }
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Set of ops the schema validates — exported for test enumeration. */
|
|
158
|
+
export const CONDUCTOR_VALIDATED_OPS = CONDUCTOR_OPS
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* @param {string} op
|
|
162
|
+
* @param {Record<string, unknown>} d
|
|
163
|
+
* @param {string[]} fields
|
|
164
|
+
* @returns {{ ok: true } | { ok: false, error: string }}
|
|
165
|
+
*/
|
|
166
|
+
function requireFields(op, d, fields) {
|
|
167
|
+
const missing = fields.filter((f) => {
|
|
168
|
+
const v = d[f]
|
|
169
|
+
if (typeof v !== 'string' || v.length === 0) return true
|
|
170
|
+
return false
|
|
171
|
+
})
|
|
172
|
+
if (missing.length === 0) return { ok: true }
|
|
173
|
+
return { ok: false, error: `${op} missing required field(s): ${missing.join(', ')}` }
|
|
174
|
+
}
|
|
@@ -32,6 +32,7 @@ import { withFileLock } from '../util/fs-lock.mjs'
|
|
|
32
32
|
import { atomicWriteJson } from '../util/io.mjs'
|
|
33
33
|
import { getAgentIdentity, resolveSharedRoot } from './agent-identity.mjs'
|
|
34
34
|
import { appendLifecycleEvent } from './event-log.mjs'
|
|
35
|
+
import { validateConductorEvent } from './event-schema.mjs'
|
|
35
36
|
import {
|
|
36
37
|
listActive as teamStatusListActive,
|
|
37
38
|
removeSelf as teamStatusRemoveSelf,
|
|
@@ -128,9 +129,22 @@ export function createCoordinationStore(projectRoot) {
|
|
|
128
129
|
* @internal — R10 append-only event log. Emits one JSONL line per
|
|
129
130
|
* call via appendLifecycleEvent. Best-effort: filesystem errors are
|
|
130
131
|
* swallowed so a missing events/ dir never breaks task lifecycle.
|
|
132
|
+
*
|
|
133
|
+
* Step 1 — schema gate: conductor ops written via the store seam are
|
|
134
|
+
* validated against `event-schema.mjs` before they reach disk.
|
|
135
|
+
* Schema violations throw (caught here and surfaced via console.warn)
|
|
136
|
+
* because a malformed conductor event would silently break the
|
|
137
|
+
* drawer's pairing logic — we want it loud, not best-effort.
|
|
131
138
|
*/
|
|
132
139
|
appendEvent(evt) {
|
|
133
140
|
try {
|
|
141
|
+
if (evt && typeof evt.op === 'string') {
|
|
142
|
+
const validation = validateConductorEvent(evt.op, evt.data)
|
|
143
|
+
if (!validation.ok) {
|
|
144
|
+
console.warn(`[store.appendEvent] schema violation: ${validation.error}`)
|
|
145
|
+
return null
|
|
146
|
+
}
|
|
147
|
+
}
|
|
134
148
|
return appendLifecycleEvent(projectRoot, evt)
|
|
135
149
|
} catch {
|
|
136
150
|
/* best-effort — never block a lifecycle transition on the log */
|
package/src/cli/dispatch.mjs
CHANGED
|
@@ -35,14 +35,17 @@
|
|
|
35
35
|
*/
|
|
36
36
|
|
|
37
37
|
import { cmdActive } from './commands/active.mjs'
|
|
38
|
+
import { cmdAdr } from './commands/adr.mjs'
|
|
38
39
|
import { cmdAudit } from './commands/audit.mjs'
|
|
39
40
|
import { cmdAuthorSkill } from './commands/author-skill.mjs'
|
|
40
41
|
import { cmdBootstrap } from './commands/bootstrap.mjs'
|
|
41
42
|
import { cmdCheck } from './commands/check.mjs'
|
|
42
43
|
import { cmdCheckVersion } from './commands/check-version.mjs'
|
|
44
|
+
import { cmdCiWatch } from './commands/ci-watch.mjs'
|
|
43
45
|
import { cmdCommit } from './commands/commit.mjs'
|
|
44
46
|
import { cmdConsistency } from './commands/consistency.mjs'
|
|
45
47
|
import { cmdConstitution } from './commands/constitution.mjs'
|
|
48
|
+
import { cmdContext } from './commands/context.mjs'
|
|
46
49
|
import { cmdCoverageCheck } from './commands/coverage-check.mjs'
|
|
47
50
|
import { cmdCreateDocs } from './commands/create-docs.mjs'
|
|
48
51
|
import { cmdDesignLint } from './commands/design-lint.mjs'
|
|
@@ -83,6 +86,7 @@ import { cmdTerminal } from './commands/terminal.mjs'
|
|
|
83
86
|
import { cmdTokenBudget } from './commands/token-budget.mjs'
|
|
84
87
|
import { cmdTokens } from './commands/tokens.mjs'
|
|
85
88
|
import { cmdToolchainDetect } from './commands/toolchain-detect.mjs'
|
|
89
|
+
import { cmdTriage } from './commands/triage.mjs'
|
|
86
90
|
import { cmdUninstall } from './commands/uninstall.mjs'
|
|
87
91
|
import { cmdValidateEvidence } from './commands/validate-evidence.mjs'
|
|
88
92
|
import { cmdVariantGallery } from './commands/variant-gallery.mjs'
|
|
@@ -231,7 +235,7 @@ export const DISPATCH = {
|
|
|
231
235
|
'author-skill': (args) => wrap(cmdAuthorSkill)(args[1], args.slice(2)),
|
|
232
236
|
'toolchain-detect': (args) => wrap(cmdToolchainDetect)(args[1], args.slice(2)),
|
|
233
237
|
'host-detect': () => wrap(cmdHostDetect)(),
|
|
234
|
-
reconcile: (args) => wrap(cmdReconcile)(args[1]),
|
|
238
|
+
reconcile: (args) => wrap(cmdReconcile)(args[1], args.slice(2)),
|
|
235
239
|
standards: (args) => wrap(cmdStandards)(args[1], args.slice(2)),
|
|
236
240
|
'precompact-hook': (args) => wrap(cmdPrecompactHook)(args[1]),
|
|
237
241
|
'migrate-pr-reviews': (args) => wrap(cmdMigratePrReviews)(args[1]),
|
|
@@ -255,8 +259,18 @@ export const DISPATCH = {
|
|
|
255
259
|
seed: (args) => wrap(cmdSeed)(args[1], args[2], args.slice(3)),
|
|
256
260
|
workflow: (args) => wrap(cmdWorkflow)(args[1], args[2], args.slice(3)),
|
|
257
261
|
'pr-review': (args) => wrap(cmdPrReview)(args[1], args[2], args.slice(3)),
|
|
262
|
+
'ci-watch': (args) => wrap(cmdCiWatch)(args[1], args[2], args.slice(3)),
|
|
258
263
|
'plan-approval': (args) => wrap(cmdPlanApproval)(args[1], args[2], args.slice(3)),
|
|
259
264
|
|
|
265
|
+
// ---- Pocock adoption (ST-5, ST-6, ST-10, ST-13) artifact writers + discoverability ----
|
|
266
|
+
context: (args) => wrap(cmdContext)(args[1], args[2], args.slice(3)),
|
|
267
|
+
adr: (args) => wrap(cmdAdr)(args[1], args[2], args.slice(3)),
|
|
268
|
+
triage: (args) => wrap(cmdTriage)(args[1], args[2], args.slice(3)),
|
|
269
|
+
modes: async (args) => {
|
|
270
|
+
const { cmdModes } = await import('./commands/modes.mjs')
|
|
271
|
+
return wrap(cmdModes)(args[1], args[2], args.slice(3))
|
|
272
|
+
},
|
|
273
|
+
|
|
260
274
|
// ---- async cmds — wrap() awaits internally; thrown errors → fail() ----
|
|
261
275
|
'docs-audit': (args) => wrap(cmdDocsAudit)(args[1], args.slice(2)),
|
|
262
276
|
gate: (args) => wrap(cmdGate)(args[1], args[2], args.slice(3)),
|
|
@@ -101,3 +101,17 @@ export function getGate(id) {
|
|
|
101
101
|
export function __resetRegistryForTests() {
|
|
102
102
|
registry.clear()
|
|
103
103
|
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Test-only helper — uniformly-named alias for the in-process cmd harness
|
|
107
|
+
* (run-cmd.mjs). Built-in gates self-register via the `gates/index.mjs`
|
|
108
|
+
* barrel at module-import time, which ESM evaluates exactly once per
|
|
109
|
+
* process — so the harness deliberately does NOT call this by default
|
|
110
|
+
* (clearing here would leave subsequent cmd invocations looking at an empty
|
|
111
|
+
* registry). Tests that need an empty registry import this name explicitly.
|
|
112
|
+
*
|
|
113
|
+
* @internal
|
|
114
|
+
*/
|
|
115
|
+
export function __resetCaches() {
|
|
116
|
+
registry.clear()
|
|
117
|
+
}
|
package/src/cli/help.mjs
CHANGED
|
@@ -28,9 +28,10 @@ Commands:
|
|
|
28
28
|
task create <dir> --description <text> [--scope <s>] [--milestone <id>] [--phase <id>] [--roadmap-ref-url <url>] [--depends-on <refs>] [--track <T>] [--autonomy <N>] Create isolated task
|
|
29
29
|
task list <dir> [--scope <s>] [--blocked-by <id>] List active tasks (optionally filtered)
|
|
30
30
|
task get <dir> --id <task-id> Get task details and artifacts
|
|
31
|
-
task update <dir> --id <id> [--lifecycle-phase <p>] [--subtasks-total <N>] [--depends-on <refs>] Update non-linkage task state
|
|
31
|
+
task update <dir> --id <id> [--lifecycle-phase <p>] [--pr-url <url>] [--subtasks-total <N>] [--depends-on <refs>] Update non-linkage task state; --pr-url may arm ci-watch per ci_watch.after_ship
|
|
32
32
|
task move <dir> --id <id> [--to-scope <s>] [--to-milestone <id>] [--to-phase <id>] [--to-roadmap-ref-url <url>] [--reason <text>] [--force] Reassign scope/milestone/phase with history
|
|
33
33
|
task close <dir> --id <task-id> [--verdict <v>] Close task and move to completed
|
|
34
|
+
task narration-drain <dir> --task <task-id> Remove a row from state.pending_narration[] under withFileLock (called by /apt:close-task --narrate-only after a successful narrator spawn)
|
|
34
35
|
task complete-subtask <dir> --task-id <id> --subtask-id <sid|a+b+c> [--commit <sha>] [--notes <text>] Atomic plan ↔ build-progress sync
|
|
35
36
|
features-audit <dir> [--diff-files <csv>|--since <ref>] [--apply-stubs] [--format json|md] Audit features registry for unregistered feature-qualifying files
|
|
36
37
|
docs-audit <dir> [--diff-files <csv>|--since <ref>|--dry-run] [--policy <path>] [--format json|md] Audit documented surfaces for docs-drift
|
|
@@ -130,5 +131,5 @@ Flags:
|
|
|
130
131
|
|
|
131
132
|
All output is JSON.`
|
|
132
133
|
|
|
133
|
-
process.stdout.write(help
|
|
134
|
+
process.stdout.write(`${help}\n`)
|
|
134
135
|
}
|
package/src/cli/host/detect.mjs
CHANGED
|
@@ -193,6 +193,11 @@ export function detectHost(opts = {}) {
|
|
|
193
193
|
worktrees: isClaudeCode,
|
|
194
194
|
background_agents: isClaudeCode || isCodex,
|
|
195
195
|
structured_prompts: structuredPrompts,
|
|
196
|
+
// D-03 (post-ship CI watcher) — true for hosts that ship a native
|
|
197
|
+
// recurring-task primitive the watcher can delegate to (CC's CronCreate,
|
|
198
|
+
// Pi's pi-schedule-prompt). False elsewhere; on those hosts the user
|
|
199
|
+
// re-invokes `/apt:watch-ci --tick --pr N` manually.
|
|
200
|
+
cron: isClaudeCode || cli === 'pi',
|
|
196
201
|
// True when the framework can ask Electron to spawn a sibling Aperant
|
|
197
202
|
// terminal (with its own worktree + CLI) over the Unix-domain IPC socket.
|
|
198
203
|
// Requires TERM_PROGRAM=Aperant + APERANT_IPC_SOCKET to be set by the
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type declarations for the .mjs conductor-view helper.
|
|
3
|
+
* Hand-authored — the import path points at src/.mjs directly.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface ConductorRoadmapView {
|
|
7
|
+
scopes: Array<{ scope: string; roadmap: unknown }>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function buildConductorRoadmapView(projectRoot: string): ConductorRoadmapView
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* conductor-view.mjs — read-only roadmap surface for the Aperant
|
|
3
|
+
* Conductor's `getRoadmap` tool.
|
|
4
|
+
*
|
|
5
|
+
* Wraps `loadRoadmapForScope` + `listRoadmapScopes` so the Conductor sees
|
|
6
|
+
* the same overlay-aware view every framework command sees. Direct reads
|
|
7
|
+
* of `.aperant/roadmap/*\/phases/*.json` or `milestones.json` would miss
|
|
8
|
+
* the dual-write overlay (seed_id from legacy blob, etc.) — Codex
|
|
9
|
+
* roundtable correction #3.
|
|
10
|
+
*
|
|
11
|
+
* Step 5 of Conductor v2 ship order.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { loadRoadmapForScope } from './io.mjs'
|
|
15
|
+
import { listRoadmapScopes } from './paths.mjs'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Build a flat per-scope roadmap snapshot for the Conductor.
|
|
19
|
+
*
|
|
20
|
+
* @param {string} projectRoot
|
|
21
|
+
* @returns {{ scopes: Array<{ scope: string, roadmap: unknown }> }}
|
|
22
|
+
*/
|
|
23
|
+
export function buildConductorRoadmapView(projectRoot) {
|
|
24
|
+
const scopes = listRoadmapScopes(projectRoot)
|
|
25
|
+
const out = []
|
|
26
|
+
for (const scope of scopes) {
|
|
27
|
+
const roadmap = loadRoadmapForScope(projectRoot, scope)
|
|
28
|
+
if (roadmap !== null) out.push({ scope, roadmap })
|
|
29
|
+
}
|
|
30
|
+
return { scopes: out }
|
|
31
|
+
}
|