@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,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ci-watch/lock.mjs — per-PR O_EXCL atomic lock (D-05).
|
|
3
|
+
*
|
|
4
|
+
* Mirrors Claude Code's `cronTasksLock.ts`: a per-PR `.aperant/ci-watches/{pr}.lock`
|
|
5
|
+
* file created with `openSync(..., 'wx')` (O_EXCL). Two concurrent callers
|
|
6
|
+
* cannot both create the file; the loser reads the existing lock and decides
|
|
7
|
+
* whether to declare BUSY or recover a stale lock.
|
|
8
|
+
*
|
|
9
|
+
* Stale-recovery (spec.md §8): a lock is recoverable iff
|
|
10
|
+
* (a) holder PID is dead (`!isPidAlive`), AND
|
|
11
|
+
* (b) `Date.now() - acquiredAt > ttl_seconds*1000`.
|
|
12
|
+
* Live PID alone is NEVER recoverable, even past TTL — TTL alone is not
|
|
13
|
+
* enough. Per D-05: PID-based stale recovery.
|
|
14
|
+
*
|
|
15
|
+
* Lock file shape (spec.md §8):
|
|
16
|
+
* { sessionId, pid, acquiredAt, ttl_seconds }
|
|
17
|
+
*
|
|
18
|
+
* Return shapes:
|
|
19
|
+
* acquireLock → { status: 'ok', lock: {...} }
|
|
20
|
+
* OR { status: 'busy', holder: {...} }
|
|
21
|
+
* releaseLock → boolean (true on release, false if owner mismatch / missing)
|
|
22
|
+
* readLock → lock object or null
|
|
23
|
+
*/
|
|
24
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, unlinkSync, writeSync, } from 'node:fs';
|
|
25
|
+
import { join, resolve } from 'node:path';
|
|
26
|
+
import { isPidAlive } from '../lock/pid.mjs';
|
|
27
|
+
const TTL_SECONDS = 1800; // 30 min — D-05
|
|
28
|
+
function watchesDir(root) {
|
|
29
|
+
return join(resolve(root), '.aperant', 'ci-watches');
|
|
30
|
+
}
|
|
31
|
+
function lockPath(root, pr) {
|
|
32
|
+
return join(watchesDir(root), `${pr}.lock`);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* @param {string} root
|
|
36
|
+
* @param {number|string} pr
|
|
37
|
+
*/
|
|
38
|
+
export function readLock(root, pr) {
|
|
39
|
+
const path = lockPath(root, pr);
|
|
40
|
+
if (!existsSync(path))
|
|
41
|
+
return null;
|
|
42
|
+
try {
|
|
43
|
+
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// A corrupted lock is treated as no lock — re-acquire will overwrite.
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function writeLockFile(path, payload) {
|
|
51
|
+
mkdirSync(resolve(path, '..'), { recursive: true });
|
|
52
|
+
const fd = openSync(path, 'wx');
|
|
53
|
+
try {
|
|
54
|
+
writeSync(fd, `${JSON.stringify(payload, null, 2)}\n`, 0, 'utf-8');
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
closeSync(fd);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Try to acquire the per-PR lock. On EEXIST, parse the existing lock — if the
|
|
62
|
+
* holder PID is dead AND TTL has elapsed, unlink + retry once. Otherwise
|
|
63
|
+
* return `{status:'busy', holder}`.
|
|
64
|
+
*
|
|
65
|
+
* @param {string} root
|
|
66
|
+
* @param {number|string} pr
|
|
67
|
+
* @param {string} sessionId
|
|
68
|
+
* @param {number} [pid=process.pid]
|
|
69
|
+
*/
|
|
70
|
+
export function acquireLock(root, pr, sessionId, pid = process.pid) {
|
|
71
|
+
const path = lockPath(root, pr);
|
|
72
|
+
const payload = {
|
|
73
|
+
sessionId,
|
|
74
|
+
pid,
|
|
75
|
+
acquiredAt: new Date().toISOString(),
|
|
76
|
+
ttl_seconds: TTL_SECONDS,
|
|
77
|
+
};
|
|
78
|
+
try {
|
|
79
|
+
writeLockFile(path, payload);
|
|
80
|
+
return { status: 'ok', lock: payload };
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
if (err.code !== 'EEXIST')
|
|
84
|
+
throw err;
|
|
85
|
+
}
|
|
86
|
+
// Lock file exists — inspect.
|
|
87
|
+
const existing = readLock(root, pr);
|
|
88
|
+
if (!existing) {
|
|
89
|
+
// Corrupted lock — treat as recoverable, unlink + retry once.
|
|
90
|
+
try {
|
|
91
|
+
unlinkSync(path);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
/* race — another caller cleaned up */
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
writeLockFile(path, payload);
|
|
98
|
+
return { status: 'ok', lock: payload };
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
if (err.code === 'EEXIST') {
|
|
102
|
+
return { status: 'busy', holder: readLock(root, pr) };
|
|
103
|
+
}
|
|
104
|
+
throw err;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (isStale(existing)) {
|
|
108
|
+
try {
|
|
109
|
+
unlinkSync(path);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
/* race — another caller cleaned up */
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
writeLockFile(path, payload);
|
|
116
|
+
return { status: 'ok', lock: payload };
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
if (err.code === 'EEXIST') {
|
|
120
|
+
return { status: 'busy', holder: readLock(root, pr) };
|
|
121
|
+
}
|
|
122
|
+
throw err;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return { status: 'busy', holder: existing };
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Release the per-PR lock. Owner must match (`sessionId` equality). Returns
|
|
129
|
+
* `true` if released, `false` if the lock is missing or owned by someone
|
|
130
|
+
* else.
|
|
131
|
+
*
|
|
132
|
+
* @param {string} root
|
|
133
|
+
* @param {number|string} pr
|
|
134
|
+
* @param {string} sessionId
|
|
135
|
+
*/
|
|
136
|
+
export function releaseLock(root, pr, sessionId) {
|
|
137
|
+
const path = lockPath(root, pr);
|
|
138
|
+
if (!existsSync(path))
|
|
139
|
+
return false;
|
|
140
|
+
const existing = readLock(root, pr);
|
|
141
|
+
if (!existing) {
|
|
142
|
+
// Corrupted — fail open (let next caller clean up).
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
if (existing.sessionId !== sessionId)
|
|
146
|
+
return false;
|
|
147
|
+
try {
|
|
148
|
+
unlinkSync(path);
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Is the given lock stale (i.e., recoverable by a new caller)? Per D-05,
|
|
157
|
+
* BOTH conditions are required: dead PID AND past TTL.
|
|
158
|
+
*
|
|
159
|
+
* Exposed for unit tests; the orchestrator does not call this directly.
|
|
160
|
+
*
|
|
161
|
+
* @param {{ pid: number, acquiredAt: string, ttl_seconds: number }} lockData
|
|
162
|
+
*/
|
|
163
|
+
export function isStale(lockData) {
|
|
164
|
+
if (!lockData || typeof lockData !== 'object')
|
|
165
|
+
return false;
|
|
166
|
+
const ttlMs = (lockData.ttl_seconds || TTL_SECONDS) * 1000;
|
|
167
|
+
const age = Date.now() - new Date(lockData.acquiredAt).getTime();
|
|
168
|
+
if (age <= ttlMs)
|
|
169
|
+
return false;
|
|
170
|
+
return !isPidAlive(lockData.pid);
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=lock.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock.mjs","sourceRoot":"","sources":["../../../src/cli/ci-watch/lock.mjs"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read the per-PR state file. Returns `null` when the file does not exist.
|
|
3
|
+
* Returns `{ error: 'corrupted', message }` when the file exists but is not
|
|
4
|
+
* parseable JSON — callers route that to the infra-backoff branch.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} root
|
|
7
|
+
* @param {number|string} pr
|
|
8
|
+
*/
|
|
9
|
+
export function readState(root: string, pr: number | string): any;
|
|
10
|
+
/**
|
|
11
|
+
* Atomically write the per-PR state file. Caller passes the full state object
|
|
12
|
+
* — this function does not merge, it overwrites. Use `readState` first if a
|
|
13
|
+
* merge is needed.
|
|
14
|
+
*
|
|
15
|
+
* @param {string} root
|
|
16
|
+
* @param {number|string} pr
|
|
17
|
+
* @param {object} state
|
|
18
|
+
*/
|
|
19
|
+
export function writeState(root: string, pr: number | string, state: object): void;
|
|
20
|
+
/**
|
|
21
|
+
* Delete the per-PR state file. No-op when the file does not exist.
|
|
22
|
+
*
|
|
23
|
+
* @param {string} root
|
|
24
|
+
* @param {number|string} pr
|
|
25
|
+
*/
|
|
26
|
+
export function deleteState(root: string, pr: number | string): void;
|
|
27
|
+
/**
|
|
28
|
+
* Enumerate every per-PR state file under `.aperant/ci-watches/`. Returns an
|
|
29
|
+
* array of parsed state objects in lexicographic filename order. Skips files
|
|
30
|
+
* whose name does not match `<pr>.json` and skips corrupted files (their
|
|
31
|
+
* presence does not block the rest of the listing).
|
|
32
|
+
*
|
|
33
|
+
* @param {string} root
|
|
34
|
+
*/
|
|
35
|
+
export function listWatches(root: string): any[];
|
|
36
|
+
//# sourceMappingURL=state.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.d.mts","sourceRoot":"","sources":["../../../src/cli/ci-watch/state.mjs"],"names":[],"mappings":"AAsCA;;;;;;;GAOG;AACH,gCAHW,MAAM,MACN,MAAM,GAAC,MAAM,OAUvB;AAED;;;;;;;;GAQG;AACH,iCAJW,MAAM,MACN,MAAM,GAAC,MAAM,SACb,MAAM,QAIhB;AAED;;;;;GAKG;AACH,kCAHW,MAAM,MACN,MAAM,GAAC,MAAM,QAKvB;AAED;;;;;;;GAOG;AACH,kCAFW,MAAM,SAgBhB"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ci-watch/state.mjs — per-PR state-file primitives for the /apt:watch-ci
|
|
3
|
+
* post-ship watcher (D-04).
|
|
4
|
+
*
|
|
5
|
+
* Storage shape (`.aperant/ci-watches/{pr}.json`), spec.md §8:
|
|
6
|
+
*
|
|
7
|
+
* {
|
|
8
|
+
* pr, task_id, host, scheduler_handle | null,
|
|
9
|
+
* attempts, started_at, deadline, last_poll,
|
|
10
|
+
* last_status | null, next_tick_interval_ms,
|
|
11
|
+
* paused, auto_merge_on_green
|
|
12
|
+
* }
|
|
13
|
+
*
|
|
14
|
+
* This module is pure read/write — classification logic lives in
|
|
15
|
+
* stop-matrix.mjs, the orchestrator lives in commands/ci-watch.mjs.
|
|
16
|
+
*
|
|
17
|
+
* Crash-safety: `writeState` goes through `atomicWriteJson` (tmp + fsync +
|
|
18
|
+
* rename). `readState` returns `null` for a missing file and never throws on
|
|
19
|
+
* a missing path — callers can distinguish "no watch yet" from "watch is
|
|
20
|
+
* corrupted".
|
|
21
|
+
*
|
|
22
|
+
* The corrupted-JSON branch returns a recoverable error envelope rather than
|
|
23
|
+
* throwing so the orchestrator can surface it without crashing the cron
|
|
24
|
+
* payload mid-tick.
|
|
25
|
+
*/
|
|
26
|
+
import { existsSync, readdirSync, readFileSync, unlinkSync } from 'node:fs';
|
|
27
|
+
import { join, resolve } from 'node:path';
|
|
28
|
+
import { atomicWriteJson } from '../util/io.mjs';
|
|
29
|
+
function watchesDir(root) {
|
|
30
|
+
return join(resolve(root), '.aperant', 'ci-watches');
|
|
31
|
+
}
|
|
32
|
+
function stateFile(root, pr) {
|
|
33
|
+
return join(watchesDir(root), `${pr}.json`);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Read the per-PR state file. Returns `null` when the file does not exist.
|
|
37
|
+
* Returns `{ error: 'corrupted', message }` when the file exists but is not
|
|
38
|
+
* parseable JSON — callers route that to the infra-backoff branch.
|
|
39
|
+
*
|
|
40
|
+
* @param {string} root
|
|
41
|
+
* @param {number|string} pr
|
|
42
|
+
*/
|
|
43
|
+
export function readState(root, pr) {
|
|
44
|
+
const path = stateFile(root, pr);
|
|
45
|
+
if (!existsSync(path))
|
|
46
|
+
return null;
|
|
47
|
+
try {
|
|
48
|
+
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
return { error: 'corrupted', message: err.message };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Atomically write the per-PR state file. Caller passes the full state object
|
|
56
|
+
* — this function does not merge, it overwrites. Use `readState` first if a
|
|
57
|
+
* merge is needed.
|
|
58
|
+
*
|
|
59
|
+
* @param {string} root
|
|
60
|
+
* @param {number|string} pr
|
|
61
|
+
* @param {object} state
|
|
62
|
+
*/
|
|
63
|
+
export function writeState(root, pr, state) {
|
|
64
|
+
atomicWriteJson(stateFile(root, pr), state);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Delete the per-PR state file. No-op when the file does not exist.
|
|
68
|
+
*
|
|
69
|
+
* @param {string} root
|
|
70
|
+
* @param {number|string} pr
|
|
71
|
+
*/
|
|
72
|
+
export function deleteState(root, pr) {
|
|
73
|
+
const path = stateFile(root, pr);
|
|
74
|
+
if (existsSync(path))
|
|
75
|
+
unlinkSync(path);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Enumerate every per-PR state file under `.aperant/ci-watches/`. Returns an
|
|
79
|
+
* array of parsed state objects in lexicographic filename order. Skips files
|
|
80
|
+
* whose name does not match `<pr>.json` and skips corrupted files (their
|
|
81
|
+
* presence does not block the rest of the listing).
|
|
82
|
+
*
|
|
83
|
+
* @param {string} root
|
|
84
|
+
*/
|
|
85
|
+
export function listWatches(root) {
|
|
86
|
+
const dir = watchesDir(root);
|
|
87
|
+
if (!existsSync(dir))
|
|
88
|
+
return [];
|
|
89
|
+
const watches = [];
|
|
90
|
+
for (const f of readdirSync(dir).sort()) {
|
|
91
|
+
if (!/^\d+\.json$/.test(f))
|
|
92
|
+
continue;
|
|
93
|
+
try {
|
|
94
|
+
watches.push(JSON.parse(readFileSync(join(dir, f), 'utf-8')));
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// Skip corrupted state files — listWatches is a read-only enumerator
|
|
98
|
+
// and must not throw on partial corruption.
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return watches;
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=state.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.mjs","sourceRoot":"","sources":["../../../src/cli/ci-watch/state.mjs"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAC3E,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAEhD,SAAS,UAAU,CAAC,IAAI;IACvB,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,YAAY,CAAC,CAAA;AACrD,CAAC;AAED,SAAS,SAAS,CAAC,IAAI,EAAE,EAAE;IAC1B,OAAO,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,CAAA;AAC5C,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS,CAAC,IAAI,EAAE,EAAE;IACjC,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;IAChC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IAClC,IAAI,CAAC;QACJ,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAA;IAC/C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAA;IACpD,CAAC;AACF,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,UAAU,CAAC,IAAI,EAAE,EAAE,EAAE,KAAK;IACzC,eAAe,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,KAAK,CAAC,CAAA;AAC5C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,IAAI,EAAE,EAAE;IACnC,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;IAChC,IAAI,UAAU,CAAC,IAAI,CAAC;QAAE,UAAU,CAAC,IAAI,CAAC,CAAA;AACvC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,IAAI;IAC/B,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,CAAA;IAC5B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,CAAA;IAC/B,MAAM,OAAO,GAAG,EAAE,CAAA;IAClB,KAAK,MAAM,CAAC,IAAI,WAAW,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QACzC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC;YAAE,SAAQ;QACpC,IAAI,CAAC;YACJ,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;QAC9D,CAAC;QAAC,MAAM,CAAC;YACR,qEAAqE;YACrE,4CAA4C;QAC7C,CAAC;IACF,CAAC;IACD,OAAO,OAAO,CAAA;AACf,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} CheckRow
|
|
3
|
+
* @property {string} [name]
|
|
4
|
+
* @property {string} [conclusion] e.g. 'success' | 'failure' | 'cancelled' | 'timed_out' | 'neutral'
|
|
5
|
+
* @property {string} [state]
|
|
6
|
+
* @property {string} [bucket]
|
|
7
|
+
* @property {string} [failure_class] — optional pre-classified hint (test-only seam)
|
|
8
|
+
* @property {string} [log_fingerprint] — optional digest for identical-log detection
|
|
9
|
+
*
|
|
10
|
+
* @typedef {Object} ClassifyInput
|
|
11
|
+
* @property {CheckRow[]} checks
|
|
12
|
+
* @property {Object} state — per-PR state (see state.mjs §8)
|
|
13
|
+
* @property {Object} prMetadata — { state: 'OPEN'|'CLOSED'|'MERGED', reviewDecision?: string }
|
|
14
|
+
*
|
|
15
|
+
* @typedef {Object} ClassifyOutput
|
|
16
|
+
* @property {string} action
|
|
17
|
+
* @property {boolean} counts_toward_budget
|
|
18
|
+
* @property {string} reason
|
|
19
|
+
*
|
|
20
|
+
* @param {ClassifyInput} input
|
|
21
|
+
* @returns {ClassifyOutput}
|
|
22
|
+
*/
|
|
23
|
+
export function classify({ checks, state, prMetadata }: ClassifyInput): ClassifyOutput;
|
|
24
|
+
export function isFailing(check: any): boolean;
|
|
25
|
+
export type CheckRow = {
|
|
26
|
+
name?: string | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* e.g. 'success' | 'failure' | 'cancelled' | 'timed_out' | 'neutral'
|
|
29
|
+
*/
|
|
30
|
+
conclusion?: string | undefined;
|
|
31
|
+
state?: string | undefined;
|
|
32
|
+
bucket?: string | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* — optional pre-classified hint (test-only seam)
|
|
35
|
+
*/
|
|
36
|
+
failure_class?: string | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* — optional digest for identical-log detection
|
|
39
|
+
*/
|
|
40
|
+
log_fingerprint?: string | undefined;
|
|
41
|
+
};
|
|
42
|
+
export type ClassifyInput = {
|
|
43
|
+
checks: CheckRow[];
|
|
44
|
+
/**
|
|
45
|
+
* — per-PR state (see state.mjs §8)
|
|
46
|
+
*/
|
|
47
|
+
state: Object;
|
|
48
|
+
/**
|
|
49
|
+
* — { state: 'OPEN'|'CLOSED'|'MERGED', reviewDecision?: string }
|
|
50
|
+
*/
|
|
51
|
+
prMetadata: Object;
|
|
52
|
+
};
|
|
53
|
+
export type ClassifyOutput = {
|
|
54
|
+
action: string;
|
|
55
|
+
counts_toward_budget: boolean;
|
|
56
|
+
reason: string;
|
|
57
|
+
};
|
|
58
|
+
//# sourceMappingURL=stop-matrix.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stop-matrix.d.mts","sourceRoot":"","sources":["../../../src/cli/ci-watch/stop-matrix.mjs"],"names":[],"mappings":"AA8BA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wDAHW,aAAa,GACX,cAAc,CA6H1B;AAED,+CAGC;;;;;;;;;;;;;;;;;;;YA5Ia,QAAQ,EAAE;;;;WACV,MAAM;;;;gBACN,MAAM;;;YAGN,MAAM;0BACN,OAAO;YACP,MAAM"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ci-watch/stop-matrix.mjs — pure stop-matrix classifier (D-06).
|
|
3
|
+
*
|
|
4
|
+
* `classify({checks, state, prMetadata})` returns
|
|
5
|
+
* { action, counts_toward_budget, reason }
|
|
6
|
+
*
|
|
7
|
+
* No I/O, no fs, no gh CLI shell-outs — caller pre-fetches `checks` (the
|
|
8
|
+
* payload from `gh pr checks --json name,bucket,state,conclusion,detailsUrl`)
|
|
9
|
+
* and `prMetadata` (`gh pr view --json state,reviewDecision`) and passes them
|
|
10
|
+
* in. The orchestrator in commands/ci-watch.mjs handles the side effects
|
|
11
|
+
* implied by the action.
|
|
12
|
+
*
|
|
13
|
+
* Predicate order matters — short-circuits go first (merged/closed/green/cap
|
|
14
|
+
* fire before any per-check inspection). See spec.md §9 table for the full
|
|
15
|
+
* ordering.
|
|
16
|
+
*
|
|
17
|
+
* The 11 actions:
|
|
18
|
+
* merged, closed, green, attempts-cap, wall-cap, reviewer-pause,
|
|
19
|
+
* identical-log, flake-skip, infra-backoff, diverged-rebased,
|
|
20
|
+
* diverged-bailed, fix
|
|
21
|
+
*
|
|
22
|
+
* `counts_toward_budget` is `true` for fix attempts (including rebase
|
|
23
|
+
* attempts and rebase-bail); `false` for flake-skip / infra-backoff; `false`
|
|
24
|
+
* (with a sentinel meaning "n/a") for terminal actions.
|
|
25
|
+
*/
|
|
26
|
+
const ATTEMPTS_CAP = 5;
|
|
27
|
+
const INFRA_FAILURE_CLASSES = new Set(['runner-oom', 'runner-cancelled', 'gh-5xx', 'timeout']);
|
|
28
|
+
/**
|
|
29
|
+
* @typedef {Object} CheckRow
|
|
30
|
+
* @property {string} [name]
|
|
31
|
+
* @property {string} [conclusion] e.g. 'success' | 'failure' | 'cancelled' | 'timed_out' | 'neutral'
|
|
32
|
+
* @property {string} [state]
|
|
33
|
+
* @property {string} [bucket]
|
|
34
|
+
* @property {string} [failure_class] — optional pre-classified hint (test-only seam)
|
|
35
|
+
* @property {string} [log_fingerprint] — optional digest for identical-log detection
|
|
36
|
+
*
|
|
37
|
+
* @typedef {Object} ClassifyInput
|
|
38
|
+
* @property {CheckRow[]} checks
|
|
39
|
+
* @property {Object} state — per-PR state (see state.mjs §8)
|
|
40
|
+
* @property {Object} prMetadata — { state: 'OPEN'|'CLOSED'|'MERGED', reviewDecision?: string }
|
|
41
|
+
*
|
|
42
|
+
* @typedef {Object} ClassifyOutput
|
|
43
|
+
* @property {string} action
|
|
44
|
+
* @property {boolean} counts_toward_budget
|
|
45
|
+
* @property {string} reason
|
|
46
|
+
*
|
|
47
|
+
* @param {ClassifyInput} input
|
|
48
|
+
* @returns {ClassifyOutput}
|
|
49
|
+
*/
|
|
50
|
+
export function classify({ checks, state, prMetadata }) {
|
|
51
|
+
const pr = prMetadata || {};
|
|
52
|
+
const st = state || {};
|
|
53
|
+
const rows = Array.isArray(checks) ? checks : [];
|
|
54
|
+
// 1. MERGED.
|
|
55
|
+
if (pr.state === 'MERGED') {
|
|
56
|
+
return { action: 'merged', counts_toward_budget: false, reason: 'pr is merged' };
|
|
57
|
+
}
|
|
58
|
+
// 2. CLOSED (not merged).
|
|
59
|
+
if (pr.state === 'CLOSED') {
|
|
60
|
+
return { action: 'closed', counts_toward_budget: false, reason: 'pr is closed unmerged' };
|
|
61
|
+
}
|
|
62
|
+
const failed = rows.filter((c) => isFailing(c));
|
|
63
|
+
const allGreen = rows.length > 0 && rows.every((c) => c.conclusion === 'success');
|
|
64
|
+
// 3. All checks green.
|
|
65
|
+
if (allGreen) {
|
|
66
|
+
return { action: 'green', counts_toward_budget: false, reason: 'all checks succeeded' };
|
|
67
|
+
}
|
|
68
|
+
// 3b. No checks registered yet — CI hasn't enqueued jobs. Wait without
|
|
69
|
+
// burning a fix attempt.
|
|
70
|
+
if (rows.length === 0) {
|
|
71
|
+
return { action: 'wait', counts_toward_budget: false, reason: 'no checks registered yet' };
|
|
72
|
+
}
|
|
73
|
+
// 4. Attempts cap (only when red present — otherwise green/merged/closed would have fired).
|
|
74
|
+
const attempts = st.attempts || 0;
|
|
75
|
+
if (attempts >= ATTEMPTS_CAP && failed.length > 0) {
|
|
76
|
+
return {
|
|
77
|
+
action: 'attempts-cap',
|
|
78
|
+
counts_toward_budget: false,
|
|
79
|
+
reason: `${attempts} fix attempts used; cap is ${ATTEMPTS_CAP}`,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
// 5. Wall-clock cap.
|
|
83
|
+
if (st.deadline && Date.now() > new Date(st.deadline).getTime() && failed.length > 0) {
|
|
84
|
+
return {
|
|
85
|
+
action: 'wall-cap',
|
|
86
|
+
counts_toward_budget: false,
|
|
87
|
+
reason: 'past 4h deadline',
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
// 6. Reviewer asked for changes.
|
|
91
|
+
if (pr.reviewDecision === 'CHANGES_REQUESTED') {
|
|
92
|
+
return {
|
|
93
|
+
action: 'reviewer-pause',
|
|
94
|
+
counts_toward_budget: false,
|
|
95
|
+
reason: 'reviewer requested changes — pausing',
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
// 7. Identical-log → non-converging fixer.
|
|
99
|
+
// Compare aggregate fingerprints (same format as the write side in ci-watch.mjs).
|
|
100
|
+
if (attempts > 0 && st.last_status && failed.length > 0) {
|
|
101
|
+
const currentFingerprint = failed
|
|
102
|
+
.map((f) => `${f.name}:${f.log_fingerprint ?? ''}`)
|
|
103
|
+
.sort()
|
|
104
|
+
.join('|');
|
|
105
|
+
if (currentFingerprint === st.last_status) {
|
|
106
|
+
return {
|
|
107
|
+
action: 'identical-log',
|
|
108
|
+
counts_toward_budget: false,
|
|
109
|
+
reason: 'failed-check fingerprint identical to last attempt',
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// 8. Flake-skip — same check failed then passed on rerun, no commit between.
|
|
114
|
+
// The classifier sees this as an explicit hint from the caller: failed rows
|
|
115
|
+
// classified as `bucket === 'flake'` or `failure_class === 'flake'` are the
|
|
116
|
+
// flaky-test variety and must not burn a fix attempt. Fires when EVERY
|
|
117
|
+
// remaining failure is a flake — if any non-flake failure is present, we
|
|
118
|
+
// fall through and the real failure governs.
|
|
119
|
+
if (failed.length > 0 &&
|
|
120
|
+
failed.every((c) => c.bucket === 'flake' || c.failure_class === 'flake')) {
|
|
121
|
+
return {
|
|
122
|
+
action: 'flake-skip',
|
|
123
|
+
counts_toward_budget: false,
|
|
124
|
+
reason: 'check transient — passed on rerun',
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
// 9. Infra failure class.
|
|
128
|
+
if (failed.some((c) => INFRA_FAILURE_CLASSES.has(c.failure_class))) {
|
|
129
|
+
return {
|
|
130
|
+
action: 'infra-backoff',
|
|
131
|
+
counts_toward_budget: false,
|
|
132
|
+
reason: 'infra-class failure — backoff and retry',
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
// 10. Diverged branch — caller has already done the fetch + dry-rebase and
|
|
136
|
+
// passes the outcome in `prMetadata.diverged`:
|
|
137
|
+
// { rebased: true } → action 'diverged-rebased' (counts)
|
|
138
|
+
// { rebased: false } → action 'diverged-bailed' (counts)
|
|
139
|
+
if (pr.diverged && typeof pr.diverged === 'object') {
|
|
140
|
+
if (pr.diverged.rebased === true) {
|
|
141
|
+
return {
|
|
142
|
+
action: 'diverged-rebased',
|
|
143
|
+
counts_toward_budget: true,
|
|
144
|
+
reason: 'origin ahead; rebased successfully',
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
action: 'diverged-bailed',
|
|
149
|
+
counts_toward_budget: true,
|
|
150
|
+
reason: 'origin ahead; rebase conflict',
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
// 11. Default red — go fix.
|
|
154
|
+
return {
|
|
155
|
+
action: 'fix',
|
|
156
|
+
counts_toward_budget: true,
|
|
157
|
+
reason: 'red checks present — attempting fix',
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
export function isFailing(check) {
|
|
161
|
+
const c = check.conclusion;
|
|
162
|
+
return c === 'failure' || c === 'cancelled' || c === 'timed_out' || c === 'action_required';
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=stop-matrix.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stop-matrix.mjs","sourceRoot":"","sources":["../../../src/cli/ci-watch/stop-matrix.mjs"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ci-watch.d.mts","sourceRoot":"","sources":["../../../src/cli/cli-wrappers/ci-watch.mjs"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli-wrappers/ci-watch.mjs — Layer 2 wrapper for the ci-watch cmd.
|
|
3
|
+
*
|
|
4
|
+
* Today this is a passthrough — DISPATCH calls wrap(cmdCiWatch) directly.
|
|
5
|
+
* Re-exporting under cliCiWatch keeps the file-per-cmd structural seam
|
|
6
|
+
* (mirrors task.mjs / pr-review.mjs).
|
|
7
|
+
*/
|
|
8
|
+
export { cmdCiWatch as cliCiWatch } from '../commands/ci-watch.mjs';
|
|
9
|
+
//# sourceMappingURL=ci-watch.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ci-watch.mjs","sourceRoot":"","sources":["../../../src/cli/cli-wrappers/ci-watch.mjs"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,UAAU,IAAI,UAAU,EAAE,MAAM,0BAA0B,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adr.d.mts","sourceRoot":"","sources":["../../../src/cli/commands/adr.mjs"],"names":[],"mappings":"AAmIA;;GAEG;AACH,qHA4GC"}
|