@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
|
@@ -2,16 +2,18 @@
|
|
|
2
2
|
* run-cmd.mjs — D-05 in-process command harness.
|
|
3
3
|
*
|
|
4
4
|
* `runCmd(cmdFn, args, opts)` invokes a Layer 1 command function in-process
|
|
5
|
-
* (no subprocess), captures stdout, traps any process.exit calls
|
|
6
|
-
* try/finally restore),
|
|
5
|
+
* (no subprocess), captures stdout + stderr, traps any process.exit calls
|
|
6
|
+
* (via a try/finally restore), stubs `process.stdin.isTTY` /
|
|
7
|
+
* `process.stdout.isTTY` to `false` for the trapped invocation duration, and
|
|
8
|
+
* returns:
|
|
7
9
|
*
|
|
8
|
-
* { result, stdout, exitCode }
|
|
10
|
+
* { result, stdout, stderr, exitCode }
|
|
9
11
|
*
|
|
10
12
|
* `result` is the cmd function's return value (typically `{envelope, exitCode}`
|
|
11
13
|
* after the migration; `null` if the cmd called process.exit before returning).
|
|
12
|
-
* `stdout`
|
|
13
|
-
* effective exit code: the trapped exit code if process.exit was called,
|
|
14
|
-
* `result.exitCode`, else 0.
|
|
14
|
+
* `stdout` / `stderr` are the captured `*.write` payloads (string). `exitCode`
|
|
15
|
+
* is the effective exit code: the trapped exit code if process.exit was called,
|
|
16
|
+
* else `result.exitCode`, else 0.
|
|
15
17
|
*
|
|
16
18
|
* D-05 (d) decision: strictExit DEFAULTS TO TRUE — the harness throws a
|
|
17
19
|
* strict-mode-violation Error any time a Layer 1 cmd calls process.exit.
|
|
@@ -19,10 +21,23 @@
|
|
|
19
21
|
* `{ strictExit: false }` until the underlying cmd is rewritten. After Wave 5
|
|
20
22
|
* (subtask 25) zero `strictExit: false` opt-outs remain in __tests__/.
|
|
21
23
|
*
|
|
22
|
-
* Restoration: process.exit
|
|
23
|
-
*
|
|
24
|
-
*
|
|
24
|
+
* Restoration: process.exit, process.stdout.write, process.stderr.write, and
|
|
25
|
+
* the two `isTTY` bits are saved at entry and restored unconditionally in a
|
|
26
|
+
* try/finally. A test that crashes mid-call still leaves the global state
|
|
27
|
+
* byte-identical for the next test in the file.
|
|
28
|
+
*
|
|
29
|
+
* Cache resets (F3 subtask 1): by default the harness clears the two
|
|
30
|
+
* path-keyed module-graph caches that can leak across cmd invocations within
|
|
31
|
+
* a single vitest worker — `skill-discover._discoverCache` and the
|
|
32
|
+
* `agent-identity` identity cache. The `gate/registry` and
|
|
33
|
+
* `consistency/registry` registries are NOT cleared by default because their
|
|
34
|
+
* built-in entries are populated by barrel imports that ESM evaluates exactly
|
|
35
|
+
* ONCE per process — clearing them would leave subsequent cmd invocations
|
|
36
|
+
* looking at an empty registry. Tests that specifically need an empty
|
|
37
|
+
* registry import `__resetCaches()` from those modules directly.
|
|
25
38
|
*/
|
|
39
|
+
import { __resetCaches as __resetAgentIdentityCaches } from '../cli/coordination/agent-identity.mjs';
|
|
40
|
+
import { __resetCaches as __resetSkillDiscoverCaches } from '../cli/route/skill-discover.mjs';
|
|
26
41
|
class ExitTrap extends Error {
|
|
27
42
|
constructor(code) {
|
|
28
43
|
super(`apt-tools cmd called process.exit(${code})`);
|
|
@@ -32,18 +47,26 @@ class ExitTrap extends Error {
|
|
|
32
47
|
/**
|
|
33
48
|
* @param {Function} cmdFn — the Layer 1 cmd function (sync or async).
|
|
34
49
|
* @param {unknown[]} [args=[]] — argv-shaped positional args.
|
|
35
|
-
* @param {{ strictExit?: boolean, env?: Record<string, string> }} [opts]
|
|
36
|
-
* @returns {Promise<{ result: unknown, stdout: string, exitCode: number }>}
|
|
50
|
+
* @param {{ strictExit?: boolean, resetCaches?: boolean, env?: Record<string, string> }} [opts]
|
|
51
|
+
* @returns {Promise<{ result: unknown, stdout: string, stderr: string, exitCode: number }>}
|
|
37
52
|
*/
|
|
38
53
|
export async function runCmd(cmdFn, args = [], opts = {}) {
|
|
39
|
-
const { strictExit = true } = opts;
|
|
54
|
+
const { strictExit = true, resetCaches = true } = opts;
|
|
55
|
+
if (resetCaches) {
|
|
56
|
+
__resetSkillDiscoverCaches();
|
|
57
|
+
__resetAgentIdentityCaches();
|
|
58
|
+
}
|
|
40
59
|
let trappedExit = null;
|
|
41
60
|
const origExit = process.exit;
|
|
42
|
-
// Save the unbound
|
|
61
|
+
// Save the unbound originals — assignment to process.stdout.write replaces
|
|
43
62
|
// the property; restoration of the original reference returns the global
|
|
44
63
|
// to byte-identical state (Function-identity check in self-tests).
|
|
45
64
|
const origWrite = process.stdout.write;
|
|
65
|
+
const origErrWrite = process.stderr.write;
|
|
66
|
+
const origStdinIsTTY = process.stdin.isTTY;
|
|
67
|
+
const origStdoutIsTTY = process.stdout.isTTY;
|
|
46
68
|
const stdoutBuf = [];
|
|
69
|
+
const stderrBuf = [];
|
|
47
70
|
process.exit = (code) => {
|
|
48
71
|
trappedExit = typeof code === 'number' ? code : 0;
|
|
49
72
|
if (strictExit) {
|
|
@@ -59,6 +82,16 @@ export async function runCmd(cmdFn, args = [], opts = {}) {
|
|
|
59
82
|
stdoutBuf.push(typeof chunk === 'string' ? chunk : Buffer.from(chunk).toString('utf-8'));
|
|
60
83
|
return true;
|
|
61
84
|
};
|
|
85
|
+
process.stderr.write = (chunk, ..._rest) => {
|
|
86
|
+
stderrBuf.push(typeof chunk === 'string' ? chunk : Buffer.from(chunk).toString('utf-8'));
|
|
87
|
+
return true;
|
|
88
|
+
};
|
|
89
|
+
// F3 subtask 1 (Q3): defensively force the non-interactive branch for any
|
|
90
|
+
// cmd that gates on isTTY (init.mjs:127,167,1013 + cmdState's TTY sidecar).
|
|
91
|
+
// Under vitest workers isTTY is usually `undefined`, but stubbing to `false`
|
|
92
|
+
// removes the Node-version drift risk.
|
|
93
|
+
process.stdin.isTTY = false;
|
|
94
|
+
process.stdout.isTTY = false;
|
|
62
95
|
try {
|
|
63
96
|
const result = await cmdFn(...args);
|
|
64
97
|
const exitCode = trappedExit !== null
|
|
@@ -66,11 +99,16 @@ export async function runCmd(cmdFn, args = [], opts = {}) {
|
|
|
66
99
|
: typeof result === 'object' && result !== null && typeof result.exitCode === 'number'
|
|
67
100
|
? result.exitCode
|
|
68
101
|
: 0;
|
|
69
|
-
return { result, stdout: stdoutBuf.join(''), exitCode };
|
|
102
|
+
return { result, stdout: stdoutBuf.join(''), stderr: stderrBuf.join(''), exitCode };
|
|
70
103
|
}
|
|
71
104
|
catch (e) {
|
|
72
105
|
if (e instanceof ExitTrap) {
|
|
73
|
-
return {
|
|
106
|
+
return {
|
|
107
|
+
result: null,
|
|
108
|
+
stdout: stdoutBuf.join(''),
|
|
109
|
+
stderr: stderrBuf.join(''),
|
|
110
|
+
exitCode: e.code,
|
|
111
|
+
};
|
|
74
112
|
}
|
|
75
113
|
// Re-throw non-trap errors (including the strict-mode-violation Error
|
|
76
114
|
// thrown above) so tests can assert via expect(...).rejects.
|
|
@@ -79,6 +117,9 @@ export async function runCmd(cmdFn, args = [], opts = {}) {
|
|
|
79
117
|
finally {
|
|
80
118
|
process.exit = origExit;
|
|
81
119
|
process.stdout.write = origWrite;
|
|
120
|
+
process.stderr.write = origErrWrite;
|
|
121
|
+
process.stdin.isTTY = origStdinIsTTY;
|
|
122
|
+
process.stdout.isTTY = origStdoutIsTTY;
|
|
82
123
|
}
|
|
83
124
|
}
|
|
84
125
|
//# sourceMappingURL=run-cmd.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-cmd.mjs","sourceRoot":"","sources":["../../src/__test-helpers/run-cmd.mjs"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"run-cmd.mjs","sourceRoot":"","sources":["../../src/__test-helpers/run-cmd.mjs"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {string} root
|
|
3
|
+
* @param {number|string} pr
|
|
4
|
+
*/
|
|
5
|
+
export function readLock(root: string, pr: number | string): any;
|
|
6
|
+
/**
|
|
7
|
+
* Try to acquire the per-PR lock. On EEXIST, parse the existing lock — if the
|
|
8
|
+
* holder PID is dead AND TTL has elapsed, unlink + retry once. Otherwise
|
|
9
|
+
* return `{status:'busy', holder}`.
|
|
10
|
+
*
|
|
11
|
+
* @param {string} root
|
|
12
|
+
* @param {number|string} pr
|
|
13
|
+
* @param {string} sessionId
|
|
14
|
+
* @param {number} [pid=process.pid]
|
|
15
|
+
*/
|
|
16
|
+
export function acquireLock(root: string, pr: number | string, sessionId: string, pid?: number): {
|
|
17
|
+
status: string;
|
|
18
|
+
lock: {
|
|
19
|
+
sessionId: string;
|
|
20
|
+
pid: number;
|
|
21
|
+
acquiredAt: string;
|
|
22
|
+
ttl_seconds: number;
|
|
23
|
+
};
|
|
24
|
+
holder?: undefined;
|
|
25
|
+
} | {
|
|
26
|
+
status: string;
|
|
27
|
+
holder: any;
|
|
28
|
+
lock?: undefined;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Release the per-PR lock. Owner must match (`sessionId` equality). Returns
|
|
32
|
+
* `true` if released, `false` if the lock is missing or owned by someone
|
|
33
|
+
* else.
|
|
34
|
+
*
|
|
35
|
+
* @param {string} root
|
|
36
|
+
* @param {number|string} pr
|
|
37
|
+
* @param {string} sessionId
|
|
38
|
+
*/
|
|
39
|
+
export function releaseLock(root: string, pr: number | string, sessionId: string): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Is the given lock stale (i.e., recoverable by a new caller)? Per D-05,
|
|
42
|
+
* BOTH conditions are required: dead PID AND past TTL.
|
|
43
|
+
*
|
|
44
|
+
* Exposed for unit tests; the orchestrator does not call this directly.
|
|
45
|
+
*
|
|
46
|
+
* @param {{ pid: number, acquiredAt: string, ttl_seconds: number }} lockData
|
|
47
|
+
*/
|
|
48
|
+
export function isStale(lockData: {
|
|
49
|
+
pid: number;
|
|
50
|
+
acquiredAt: string;
|
|
51
|
+
ttl_seconds: number;
|
|
52
|
+
}): boolean;
|
|
53
|
+
//# sourceMappingURL=lock.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock.d.mts","sourceRoot":"","sources":["../../../src/cli/ci-watch/lock.mjs"],"names":[],"mappings":"AA8CA;;;GAGG;AACH,+BAHW,MAAM,MACN,MAAM,GAAC,MAAM,OAWvB;AAYD;;;;;;;;;GASG;AACH,kCALW,MAAM,MACN,MAAM,GAAC,MAAM,aACb,MAAM,QACN,MAAM;;;;;;;;;;;;;EAwDhB;AAED;;;;;;;;GAQG;AACH,kCAJW,MAAM,MACN,MAAM,GAAC,MAAM,aACb,MAAM,WAiBhB;AAED;;;;;;;GAOG;AACH,kCAFW;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,WAQlE"}
|
|
@@ -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"}
|