@aperant/framework 0.6.4 → 0.6.5
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 +64 -10
- 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/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 +59 -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 +220 -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 +108 -6
- 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 +259 -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/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/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 +1 -0
- 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/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 +11 -0
- 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/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 +125 -125
- package/prompts/coder.md +2 -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 +147 -4
- 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 +128 -2
- package/skills/apt-ship/SKILL.md +47 -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/context.mjs +244 -0
- package/src/cli/commands/event.mjs +63 -24
- package/src/cli/commands/host-detect.mjs +33 -7
- package/src/cli/commands/modes.mjs +215 -0
- package/src/cli/commands/route.mjs +1 -1
- package/src/cli/commands/task.mjs +125 -18
- package/src/cli/commands/tokens.mjs +157 -6
- package/src/cli/commands/triage.mjs +277 -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/event-log.mjs +73 -2
- package/src/cli/dispatch.mjs +15 -1
- package/src/cli/gate/registry.mjs +14 -0
- package/src/cli/help.mjs +1 -0
- package/src/cli/host/detect.mjs +5 -0
- package/src/cli/route/envelope.mjs +140 -106
- package/src/cli/route/skill-discover.mjs +12 -0
- package/src/cli/skill-author/contract.mjs +7 -0
- 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,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* commands/triage.mjs — `apt-tools triage` (Pocock adoption ST-10, AC9).
|
|
3
|
+
*
|
|
4
|
+
* 5-state triage state machine with extensible metadata for v2 OSS
|
|
5
|
+
* contributor primitives. Backend-configured: writes to
|
|
6
|
+
* `.aperant/tasks/{task-id}/triage.json` by default (local-only); other
|
|
7
|
+
* backends (github-issues, app-inbox) ship as not-implemented stubs.
|
|
8
|
+
*
|
|
9
|
+
* Subcommands:
|
|
10
|
+
* apt-tools triage init <project-dir> --id <task-id> [--category bug|enhancement] [--extra '{...}']
|
|
11
|
+
* apt-tools triage transition <project-dir> --id <task-id> --to <state> [--reason <text>] [--by user|agent]
|
|
12
|
+
* apt-tools triage get <project-dir> --id <task-id>
|
|
13
|
+
*
|
|
14
|
+
* States: needs-triage | needs-info | ready-for-agent | ready-for-human | wontfix
|
|
15
|
+
* Categories: bug | enhancement
|
|
16
|
+
*
|
|
17
|
+
* Fast Path Guarantee (ID-05): QUICK-routed tasks NEVER enter triage.
|
|
18
|
+
* The CLI does NOT enforce this directly — the apt:triage SKILL.md
|
|
19
|
+
* routes around QUICK tasks before invoking the CLI. The CLI is the
|
|
20
|
+
* mechanical state-machine surface; the routing is the skill body's job.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
24
|
+
import { dirname, join, resolve } from 'node:path'
|
|
25
|
+
import { parseFlags } from '../util/args.mjs'
|
|
26
|
+
import { err, exitWith, ok } from '../util/result.mjs'
|
|
27
|
+
|
|
28
|
+
const STATES = ['needs-triage', 'needs-info', 'ready-for-agent', 'ready-for-human', 'wontfix']
|
|
29
|
+
|
|
30
|
+
const CATEGORIES = ['bug', 'enhancement']
|
|
31
|
+
|
|
32
|
+
// The 8 valid transitions per spec.md AC9. Anything else is rejected.
|
|
33
|
+
const VALID_TRANSITIONS = new Set([
|
|
34
|
+
'needs-triage->needs-info',
|
|
35
|
+
'needs-triage->ready-for-agent',
|
|
36
|
+
'needs-triage->ready-for-human',
|
|
37
|
+
'needs-triage->wontfix',
|
|
38
|
+
'needs-info->ready-for-agent',
|
|
39
|
+
'needs-info->ready-for-human',
|
|
40
|
+
'needs-info->wontfix',
|
|
41
|
+
'needs-info->needs-triage',
|
|
42
|
+
])
|
|
43
|
+
|
|
44
|
+
const BACKENDS = new Set(['local-only', 'github-issues', 'app-inbox'])
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Read .aperant/config.json and extract task_tracking.backend.
|
|
48
|
+
* Defaults to 'local-only' if the file or the key is missing.
|
|
49
|
+
*
|
|
50
|
+
* @param {string} absProjectDir
|
|
51
|
+
* @returns {string}
|
|
52
|
+
*/
|
|
53
|
+
function readBackend(absProjectDir) {
|
|
54
|
+
const configPath = join(absProjectDir, '.aperant', 'config.json')
|
|
55
|
+
if (!existsSync(configPath)) return 'local-only'
|
|
56
|
+
try {
|
|
57
|
+
const parsed = JSON.parse(readFileSync(configPath, 'utf-8'))
|
|
58
|
+
const backend = parsed?.task_tracking?.backend
|
|
59
|
+
if (typeof backend === 'string' && BACKENDS.has(backend)) return backend
|
|
60
|
+
return 'local-only'
|
|
61
|
+
} catch {
|
|
62
|
+
return 'local-only'
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Triage record file path.
|
|
68
|
+
*
|
|
69
|
+
* @param {string} absProjectDir
|
|
70
|
+
* @param {string} taskId
|
|
71
|
+
*/
|
|
72
|
+
function triagePath(absProjectDir, taskId) {
|
|
73
|
+
return join(absProjectDir, '.aperant', 'tasks', taskId, 'triage.json')
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Read the current triage record, or null if it doesn't exist.
|
|
78
|
+
*
|
|
79
|
+
* @param {string} path
|
|
80
|
+
*/
|
|
81
|
+
function readTriage(path) {
|
|
82
|
+
if (!existsSync(path)) return null
|
|
83
|
+
try {
|
|
84
|
+
return JSON.parse(readFileSync(path, 'utf-8'))
|
|
85
|
+
} catch {
|
|
86
|
+
return null
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Write a triage record.
|
|
92
|
+
*
|
|
93
|
+
* @param {string} path
|
|
94
|
+
* @param {object} record
|
|
95
|
+
*/
|
|
96
|
+
function writeTriage(path, record) {
|
|
97
|
+
const dir = dirname(path)
|
|
98
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
|
|
99
|
+
writeFileSync(path, `${JSON.stringify(record, null, 2)}\n`)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Apply the backend mirror step. local-only writes to disk; the other
|
|
104
|
+
* backends return a not-implemented envelope so callers can handle the
|
|
105
|
+
* stub gracefully.
|
|
106
|
+
*
|
|
107
|
+
* @param {string} backend
|
|
108
|
+
* @param {string} absProjectDir
|
|
109
|
+
* @param {string} taskId
|
|
110
|
+
* @param {object} record
|
|
111
|
+
* @returns {null | { status: 'not-implemented', backend: string, v2_issue: string }}
|
|
112
|
+
*/
|
|
113
|
+
function mirrorToBackend(backend, absProjectDir, taskId, record) {
|
|
114
|
+
if (backend === 'local-only') {
|
|
115
|
+
writeTriage(triagePath(absProjectDir, taskId), record)
|
|
116
|
+
return null
|
|
117
|
+
}
|
|
118
|
+
// v1 stubs: github-issues and app-inbox return not-implemented.
|
|
119
|
+
return { status: 'not-implemented', backend, v2_issue: '#TBD' }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Entry: `apt-tools triage <subcommand> <project-dir> [flags]`.
|
|
124
|
+
*/
|
|
125
|
+
export function cmdTriage(subcommand, projectDir, extraArgs) {
|
|
126
|
+
if (!subcommand || !['init', 'transition', 'get'].includes(subcommand)) {
|
|
127
|
+
return err(
|
|
128
|
+
'Usage: apt-tools triage <init|transition|get> <project-dir> --id <task-id> [...]',
|
|
129
|
+
1,
|
|
130
|
+
{ command: 'triage' },
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
if (!projectDir) {
|
|
134
|
+
return err('project-dir is required', 1, { command: `triage-${subcommand}` })
|
|
135
|
+
}
|
|
136
|
+
const absProjectDir = resolve(projectDir)
|
|
137
|
+
if (!existsSync(absProjectDir)) {
|
|
138
|
+
return err(`project directory not found: ${absProjectDir}`, 1, {
|
|
139
|
+
command: `triage-${subcommand}`,
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const flags = parseFlags(extraArgs ?? [])
|
|
144
|
+
const taskId = flags.get('id')
|
|
145
|
+
if (typeof taskId !== 'string' || taskId.trim() === '') {
|
|
146
|
+
return err('--id <task-id> is required', 1, { command: `triage-${subcommand}` })
|
|
147
|
+
}
|
|
148
|
+
const TASK_ID_RE = /^[0-9]{2}-[0-9]{2}-[0-9]{2}_[a-z0-9-]+-[a-z0-9]{4}$/
|
|
149
|
+
if (!TASK_ID_RE.test(taskId)) {
|
|
150
|
+
return err(`--id must match pattern <YY-MM-DD_slug-XXXX> (e.g. 24-01-15_fix-login-a3b2)`, 1, {
|
|
151
|
+
command: `triage-${subcommand}`,
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
const backend = readBackend(absProjectDir)
|
|
155
|
+
|
|
156
|
+
if (subcommand === 'init') {
|
|
157
|
+
// Optional --category, optional --extra (JSON string).
|
|
158
|
+
const category = flags.get('category')
|
|
159
|
+
if (category !== undefined && !CATEGORIES.includes(String(category))) {
|
|
160
|
+
return err(`--category must be one of: ${CATEGORIES.join(' | ')}`, 1, {
|
|
161
|
+
command: 'triage-init',
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
let extra = {}
|
|
165
|
+
const extraRaw = flags.get('extra')
|
|
166
|
+
if (typeof extraRaw === 'string' && extraRaw.length > 0) {
|
|
167
|
+
try {
|
|
168
|
+
const parsed = JSON.parse(extraRaw)
|
|
169
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
170
|
+
extra = parsed
|
|
171
|
+
} else {
|
|
172
|
+
return err('--extra must be a JSON object string', 1, { command: 'triage-init' })
|
|
173
|
+
}
|
|
174
|
+
} catch (e) {
|
|
175
|
+
return err(`--extra is not valid JSON: ${e.message}`, 1, { command: 'triage-init' })
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const existing = readTriage(triagePath(absProjectDir, taskId))
|
|
180
|
+
if (existing) {
|
|
181
|
+
return err(`triage record already exists for ${taskId}`, 1, {
|
|
182
|
+
command: 'triage-init',
|
|
183
|
+
current_state: existing.state,
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const record = {
|
|
188
|
+
task_id: taskId,
|
|
189
|
+
state: 'needs-triage',
|
|
190
|
+
category: typeof category === 'string' ? category : null,
|
|
191
|
+
transitions: [],
|
|
192
|
+
extra,
|
|
193
|
+
}
|
|
194
|
+
const stub = mirrorToBackend(backend, absProjectDir, taskId, record)
|
|
195
|
+
if (stub) return exitWith({ command: 'triage-init', ...stub }, 0)
|
|
196
|
+
return ok({ status: 'ok', command: 'triage-init', backend, record })
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (subcommand === 'transition') {
|
|
200
|
+
const toState = flags.get('to')
|
|
201
|
+
if (typeof toState !== 'string' || !STATES.includes(toState)) {
|
|
202
|
+
return err(`--to <state> is required (one of: ${STATES.join(' | ')})`, 1, {
|
|
203
|
+
command: 'triage-transition',
|
|
204
|
+
})
|
|
205
|
+
}
|
|
206
|
+
// SEC-003: cap --reason to 1000 chars to prevent unbounded disk writes.
|
|
207
|
+
const rawReason = flags.get('reason')
|
|
208
|
+
const reason = typeof rawReason === 'string' ? rawReason.slice(0, 1000) : null
|
|
209
|
+
const by =
|
|
210
|
+
typeof flags.get('by') === 'string' && ['user', 'agent'].includes(String(flags.get('by')))
|
|
211
|
+
? String(flags.get('by'))
|
|
212
|
+
: 'agent'
|
|
213
|
+
|
|
214
|
+
const current = readTriage(triagePath(absProjectDir, taskId))
|
|
215
|
+
if (!current) {
|
|
216
|
+
return err(`no triage record for ${taskId} — run \`triage init\` first`, 1, {
|
|
217
|
+
command: 'triage-transition',
|
|
218
|
+
})
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const from = current.state
|
|
222
|
+
const transitionKey = `${from}->${toState}`
|
|
223
|
+
if (!VALID_TRANSITIONS.has(transitionKey)) {
|
|
224
|
+
return exitWith(
|
|
225
|
+
{
|
|
226
|
+
status: 'rejected',
|
|
227
|
+
command: 'triage-transition',
|
|
228
|
+
reason: 'invalid-transition',
|
|
229
|
+
from,
|
|
230
|
+
to: toState,
|
|
231
|
+
valid_from_current: STATES.filter((s) => VALID_TRANSITIONS.has(`${from}->${s}`)),
|
|
232
|
+
},
|
|
233
|
+
1,
|
|
234
|
+
)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const transition = {
|
|
238
|
+
from,
|
|
239
|
+
to: toState,
|
|
240
|
+
at: new Date().toISOString(),
|
|
241
|
+
by,
|
|
242
|
+
reason,
|
|
243
|
+
}
|
|
244
|
+
// SEC-002: Only carry known fields forward — do NOT spread the full
|
|
245
|
+
// disk-backed `current` object, which could contain arbitrary unknown
|
|
246
|
+
// keys from a hand-edited triage.json that would silently propagate.
|
|
247
|
+
const next = {
|
|
248
|
+
task_id: current.task_id,
|
|
249
|
+
state: toState,
|
|
250
|
+
category: current.category,
|
|
251
|
+
extra: current.extra ?? {},
|
|
252
|
+
transitions: [...(current.transitions || []), transition],
|
|
253
|
+
}
|
|
254
|
+
const stub = mirrorToBackend(backend, absProjectDir, taskId, next)
|
|
255
|
+
if (stub) return exitWith({ command: 'triage-transition', ...stub }, 0)
|
|
256
|
+
return ok({
|
|
257
|
+
status: 'ok',
|
|
258
|
+
command: 'triage-transition',
|
|
259
|
+
backend,
|
|
260
|
+
transition,
|
|
261
|
+
record: next,
|
|
262
|
+
})
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// subcommand === 'get'
|
|
266
|
+
const path = triagePath(absProjectDir, taskId)
|
|
267
|
+
const record = readTriage(path)
|
|
268
|
+
if (!record) {
|
|
269
|
+
return exitWith({ status: 'not-found', command: 'triage-get', task_id: taskId, file: path }, 0)
|
|
270
|
+
}
|
|
271
|
+
return ok({ status: 'ok', command: 'triage-get', record, backend })
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Exported for testing.
|
|
275
|
+
export const _STATES = STATES
|
|
276
|
+
export const _CATEGORIES = CATEGORIES
|
|
277
|
+
export const _VALID_TRANSITIONS = VALID_TRANSITIONS
|
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
* post-merge-sweep.mjs — dispatch-time passive sweeper for stuck
|
|
3
3
|
* `shipped-pending-merge` tasks (C57 Piece 2).
|
|
4
4
|
*
|
|
5
|
+
* **Bookkeeping-only contract.** This hook runs the deterministic
|
|
6
|
+
* close half of the post-merge handoff: poll `gh pr view`, flip
|
|
7
|
+
* lifecycle_phase from `shipped-pending-merge` to `completed` via
|
|
8
|
+
* the same `task close` path, strip per-PR authorship overrides,
|
|
9
|
+
* and append a `pending_narration[]` ledger row. The LLM-side
|
|
10
|
+
* narration work (registry sync + ROADMAP prose) is explicitly
|
|
11
|
+
* out of scope here — the user drains the ledger via
|
|
12
|
+
* `/apt:close-task --narrate-only` (see packages/framework/skills/
|
|
13
|
+
* apt-close-task/SKILL.md §4) which is where those side effects belong.
|
|
14
|
+
*
|
|
5
15
|
* AI agents ship a PR and walk away. Tasks then rot in
|
|
6
16
|
* `shipped-pending-merge` because nothing automatic invokes
|
|
7
17
|
* `apt-tools task close-merged`. This hook runs on every `apt-tools`
|
|
@@ -11,8 +21,10 @@
|
|
|
11
21
|
* whose PRs have terminally landed.
|
|
12
22
|
*
|
|
13
23
|
* Non-blocking: never fails the command, never mutates exit code,
|
|
14
|
-
* never throws.
|
|
15
|
-
*
|
|
24
|
+
* never throws. Two stderr lines max per dispatch: the
|
|
25
|
+
* `Auto-sweeping N pending PR(s)` line on actual sweep kick, plus
|
|
26
|
+
* the `Post-merge narration pending` banner when the ledger has
|
|
27
|
+
* un-drained rows. Both lines are gated by `APT_SUPPRESS_MERGE_SWEEP=1`.
|
|
16
28
|
*
|
|
17
29
|
* Silenced by `APT_SUPPRESS_MERGE_SWEEP=1`. Skipped when
|
|
18
30
|
* `APT_INTERNAL_MERGE_SWEEP=1` is set in env (recursion guard for
|
|
@@ -90,6 +102,25 @@ export function readShippedCandidates(projectRoot) {
|
|
|
90
102
|
return candidates.length === 0 ? null : candidates
|
|
91
103
|
}
|
|
92
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Pure preflight — returns the integer length of state.pending_narration[].
|
|
107
|
+
* Returns 0 on read/parse failure or when the ledger is absent (the banner
|
|
108
|
+
* is suppressed in that case).
|
|
109
|
+
*
|
|
110
|
+
* @internal
|
|
111
|
+
*/
|
|
112
|
+
export function readPendingNarrationCount(projectRoot) {
|
|
113
|
+
const statePath = join(projectRoot, '.aperant', 'state.json')
|
|
114
|
+
if (!existsSync(statePath)) return 0
|
|
115
|
+
try {
|
|
116
|
+
const state = JSON.parse(readFileSync(statePath, 'utf-8'))
|
|
117
|
+
const ledger = state?.pending_narration
|
|
118
|
+
return Array.isArray(ledger) ? ledger.length : 0
|
|
119
|
+
} catch {
|
|
120
|
+
return 0
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
93
124
|
/**
|
|
94
125
|
* Returns true if the cooldown lock is "fresh" (last sweep too recent).
|
|
95
126
|
* Lock content is `{pid}:{ms-since-epoch}` but we only consult mtime.
|
|
@@ -221,9 +252,24 @@ export function maybeSweepShippedMergedTasks(projectRoot, command, options = {})
|
|
|
221
252
|
return { kicked: false, reason: 'no-aperant-dir' }
|
|
222
253
|
}
|
|
223
254
|
|
|
255
|
+
// Narration-pending banner — fires on EVERY dispatch cycle when
|
|
256
|
+
// the ledger has un-drained rows, regardless of whether a sweep is
|
|
257
|
+
// kicked below. Cheap read, no spawn, no cooldown. Same suppression
|
|
258
|
+
// env as the sweep banner (APT_SUPPRESS_MERGE_SWEEP=1 hides both).
|
|
259
|
+
const pendingNarration = readPendingNarrationCount(root)
|
|
260
|
+
if (pendingNarration > 0) {
|
|
261
|
+
try {
|
|
262
|
+
process.stderr.write(
|
|
263
|
+
`[apt-tools] Post-merge narration pending (${pendingNarration}) — run /apt:close-task --narrate-only to refresh features registry + ROADMAP.\n`,
|
|
264
|
+
)
|
|
265
|
+
} catch {
|
|
266
|
+
/* stderr write failure is non-fatal */
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
224
270
|
const candidates = readShippedCandidates(root)
|
|
225
271
|
if (!candidates) {
|
|
226
|
-
return { kicked: false, reason: 'no-candidates' }
|
|
272
|
+
return { kicked: false, reason: 'no-candidates', pending_narration: pendingNarration }
|
|
227
273
|
}
|
|
228
274
|
|
|
229
275
|
const lockPath = join(root, '.aperant', '.merge-sweep.lock')
|
|
@@ -82,3 +82,17 @@ export function getRule(id) {
|
|
|
82
82
|
export function __resetRegistryForTests() {
|
|
83
83
|
registry.clear()
|
|
84
84
|
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Test-only helper — uniformly-named alias for the in-process cmd harness
|
|
88
|
+
* (run-cmd.mjs). Built-in rules self-register via the `rules/index.mjs`
|
|
89
|
+
* barrel at module-import time, which ESM evaluates exactly once per
|
|
90
|
+
* process — so the harness deliberately does NOT call this by default
|
|
91
|
+
* (clearing here would leave subsequent cmd invocations looking at an empty
|
|
92
|
+
* registry). Tests that need an empty registry import this name explicitly.
|
|
93
|
+
*
|
|
94
|
+
* @internal
|
|
95
|
+
*/
|
|
96
|
+
export function __resetCaches() {
|
|
97
|
+
registry.clear()
|
|
98
|
+
}
|
|
@@ -100,3 +100,15 @@ export function getAgentIdentity(projectDir) {
|
|
|
100
100
|
export function __resetAgentIdentityCacheForTests() {
|
|
101
101
|
cache.clear()
|
|
102
102
|
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Test-only helper — uniformly-named alias for the in-process cmd harness
|
|
106
|
+
* (run-cmd.mjs), which imports `__resetCaches` from each cache host with a
|
|
107
|
+
* single consistent name. Behaviour is identical to
|
|
108
|
+
* `__resetAgentIdentityCacheForTests`.
|
|
109
|
+
*
|
|
110
|
+
* @internal
|
|
111
|
+
*/
|
|
112
|
+
export function __resetCaches() {
|
|
113
|
+
cache.clear()
|
|
114
|
+
}
|
|
@@ -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
|
+
}
|
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
|
@@ -31,6 +31,7 @@ Commands:
|
|
|
31
31
|
task update <dir> --id <id> [--lifecycle-phase <p>] [--subtasks-total <N>] [--depends-on <refs>] Update non-linkage task state
|
|
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
|
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
|