@aragon-agent/cli 0.6.43 → 0.6.45
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 +44 -0
- package/README.md +69 -2
- package/dist/agent/controller.d.ts +69 -0
- package/dist/agent/controller.js +224 -11
- package/dist/agent/governance-view.d.ts +40 -0
- package/dist/agent/governance-view.js +40 -0
- package/dist/agent/governance-wiring.d.ts +162 -0
- package/dist/agent/governance-wiring.js +470 -0
- package/dist/agent/plan-prompt.d.ts +7 -0
- package/dist/agent/plan-prompt.js +2 -1
- package/dist/agent/reducer.d.ts +33 -1
- package/dist/agent/reducer.js +45 -9
- package/dist/agent/system-prompt.d.ts +19 -0
- package/dist/agent/system-prompt.js +9 -1
- package/dist/cli.js +45 -1
- package/dist/commands/builtins.js +10 -4
- package/dist/commands/governance-command.d.ts +35 -0
- package/dist/commands/governance-command.js +292 -0
- package/dist/commands/governance-runs-table.d.ts +40 -0
- package/dist/commands/governance-runs-table.js +130 -0
- package/dist/commands/registry.d.ts +12 -0
- package/dist/config/app-paths.d.ts +10 -0
- package/dist/config/app-paths.js +12 -0
- package/dist/config/cli-commands.d.ts +18 -0
- package/dist/config/cli-commands.js +47 -1
- package/dist/config/env.d.ts +2 -1
- package/dist/config/env.js +10 -1
- package/dist/config/governance-config.d.ts +125 -0
- package/dist/config/governance-config.js +164 -0
- package/dist/config/governance-launch.d.ts +71 -0
- package/dist/config/governance-launch.js +58 -0
- package/dist/config/load.d.ts +15 -0
- package/dist/config/load.js +26 -1
- package/dist/config/model-profile-store.d.ts +4 -2
- package/dist/config/schema.d.ts +22 -0
- package/dist/config/schema.js +6 -0
- package/dist/config/store.js +14 -1
- package/dist/config/ui-state.d.ts +11 -0
- package/dist/config/ui-state.js +11 -0
- package/dist/diagnostics/doctor.js +20 -0
- package/dist/governance/card-normalize.d.ts +24 -0
- package/dist/governance/card-normalize.js +76 -0
- package/dist/governance/copy.d.ts +109 -0
- package/dist/governance/copy.js +223 -0
- package/dist/governance/engine/aftermath.d.ts +49 -0
- package/dist/governance/engine/aftermath.js +134 -0
- package/dist/governance/engine/artifact-persist.d.ts +35 -0
- package/dist/governance/engine/artifact-persist.js +86 -0
- package/dist/governance/engine/budget.d.ts +52 -0
- package/dist/governance/engine/budget.js +112 -0
- package/dist/governance/engine/cards.d.ts +18 -0
- package/dist/governance/engine/cards.js +186 -0
- package/dist/governance/engine/digest.d.ts +32 -0
- package/dist/governance/engine/digest.js +63 -0
- package/dist/governance/engine/engine-core.d.ts +219 -0
- package/dist/governance/engine/engine-core.js +562 -0
- package/dist/governance/engine/engine.d.ts +68 -0
- package/dist/governance/engine/engine.js +314 -0
- package/dist/governance/engine/estimate.d.ts +38 -0
- package/dist/governance/engine/estimate.js +46 -0
- package/dist/governance/engine/inspector.d.ts +16 -0
- package/dist/governance/engine/inspector.js +396 -0
- package/dist/governance/engine/integration.d.ts +35 -0
- package/dist/governance/engine/integration.js +220 -0
- package/dist/governance/engine/lifecycle.d.ts +29 -0
- package/dist/governance/engine/lifecycle.js +656 -0
- package/dist/governance/engine/pause.d.ts +56 -0
- package/dist/governance/engine/pause.js +107 -0
- package/dist/governance/engine/phase-members.d.ts +15 -0
- package/dist/governance/engine/phase-members.js +34 -0
- package/dist/governance/engine/phase-planner.d.ts +53 -0
- package/dist/governance/engine/phase-planner.js +282 -0
- package/dist/governance/engine/rebuild.d.ts +27 -0
- package/dist/governance/engine/rebuild.js +57 -0
- package/dist/governance/engine/report.d.ts +66 -0
- package/dist/governance/engine/report.js +220 -0
- package/dist/governance/engine/run-digest.d.ts +20 -0
- package/dist/governance/engine/run-digest.js +42 -0
- package/dist/governance/engine/spec-builder.d.ts +117 -0
- package/dist/governance/engine/spec-builder.js +321 -0
- package/dist/governance/engine/stage-runner.d.ts +22 -0
- package/dist/governance/engine/stage-runner.js +664 -0
- package/dist/governance/engine/start-input.d.ts +19 -0
- package/dist/governance/engine/start-input.js +111 -0
- package/dist/governance/engine/state-machine.d.ts +101 -0
- package/dist/governance/engine/state-machine.js +586 -0
- package/dist/governance/engine/submission-queries.d.ts +42 -0
- package/dist/governance/engine/submission-queries.js +97 -0
- package/dist/governance/engine/submission-render.d.ts +12 -0
- package/dist/governance/engine/submission-render.js +92 -0
- package/dist/governance/engine/submissions.d.ts +27 -0
- package/dist/governance/engine/submissions.js +455 -0
- package/dist/governance/engine/visibility.d.ts +104 -0
- package/dist/governance/engine/visibility.js +223 -0
- package/dist/governance/governed-dispatch.d.ts +19 -0
- package/dist/governance/governed-dispatch.js +17 -0
- package/dist/governance/limits.d.ts +107 -0
- package/dist/governance/limits.js +131 -0
- package/dist/governance/posture-transition.d.ts +38 -0
- package/dist/governance/posture-transition.js +44 -0
- package/dist/governance/posture.d.ts +35 -0
- package/dist/governance/posture.js +59 -0
- package/dist/governance/profiles.d.ts +48 -0
- package/dist/governance/profiles.js +140 -0
- package/dist/governance/prompts/orchestrator-block.d.ts +40 -0
- package/dist/governance/prompts/orchestrator-block.js +128 -0
- package/dist/governance/prompts/plan-addendum.d.ts +22 -0
- package/dist/governance/prompts/plan-addendum.js +60 -0
- package/dist/governance/prompts/refusals.d.ts +48 -0
- package/dist/governance/prompts/refusals.js +82 -0
- package/dist/governance/prompts/role-blocks.d.ts +40 -0
- package/dist/governance/prompts/role-blocks.js +255 -0
- package/dist/governance/report-reader.d.ts +31 -0
- package/dist/governance/report-reader.js +57 -0
- package/dist/governance/roles.d.ts +83 -0
- package/dist/governance/roles.js +142 -0
- package/dist/governance/rules/claims.d.ts +83 -0
- package/dist/governance/rules/claims.js +250 -0
- package/dist/governance/rules/elimination.d.ts +66 -0
- package/dist/governance/rules/elimination.js +105 -0
- package/dist/governance/rules/events-table.d.ts +46 -0
- package/dist/governance/rules/events-table.js +94 -0
- package/dist/governance/rules/failure-analysis.d.ts +28 -0
- package/dist/governance/rules/failure-analysis.js +93 -0
- package/dist/governance/rules/ledger-replay.d.ts +53 -0
- package/dist/governance/rules/ledger-replay.js +53 -0
- package/dist/governance/rules/ledger-state.d.ts +85 -0
- package/dist/governance/rules/ledger-state.js +175 -0
- package/dist/governance/rules/ledger.d.ts +228 -0
- package/dist/governance/rules/ledger.js +560 -0
- package/dist/governance/rules/rca.d.ts +68 -0
- package/dist/governance/rules/rca.js +220 -0
- package/dist/governance/rules/settlement.d.ts +129 -0
- package/dist/governance/rules/settlement.js +363 -0
- package/dist/governance/rules/suspicion.d.ts +66 -0
- package/dist/governance/rules/suspicion.js +169 -0
- package/dist/governance/store/artifacts.d.ts +81 -0
- package/dist/governance/store/artifacts.js +173 -0
- package/dist/governance/store/atomic.d.ts +79 -0
- package/dist/governance/store/atomic.js +119 -0
- package/dist/governance/store/events-log.d.ts +35 -0
- package/dist/governance/store/events-log.js +86 -0
- package/dist/governance/store/run-lock.d.ts +51 -0
- package/dist/governance/store/run-lock.js +122 -0
- package/dist/governance/store/run-store.d.ts +229 -0
- package/dist/governance/store/run-store.js +644 -0
- package/dist/governance/store/scoreboard-export.d.ts +53 -0
- package/dist/governance/store/scoreboard-export.js +65 -0
- package/dist/governance/tools/child-tools.d.ts +31 -0
- package/dist/governance/tools/child-tools.js +65 -0
- package/dist/governance/tools/guard.d.ts +71 -0
- package/dist/governance/tools/guard.js +194 -0
- package/dist/governance/tools/inspector-tools.d.ts +63 -0
- package/dist/governance/tools/inspector-tools.js +424 -0
- package/dist/governance/tools/lead-tools.d.ts +42 -0
- package/dist/governance/tools/lead-tools.js +226 -0
- package/dist/governance/tools/normalize-util.d.ts +101 -0
- package/dist/governance/tools/normalize-util.js +230 -0
- package/dist/governance/tools/scan-incomplete.d.ts +79 -0
- package/dist/governance/tools/scan-incomplete.js +486 -0
- package/dist/governance/tools/shared-tools.d.ts +44 -0
- package/dist/governance/tools/shared-tools.js +125 -0
- package/dist/governance/tools/submit-normalizers.d.ts +28 -0
- package/dist/governance/tools/submit-normalizers.js +474 -0
- package/dist/governance/tools/submit-tools.d.ts +26 -0
- package/dist/governance/tools/submit-tools.js +171 -0
- package/dist/governance/types.d.ts +661 -0
- package/dist/governance/types.js +27 -0
- package/dist/proc/kill-tree.d.ts +19 -1
- package/dist/proc/kill-tree.js +32 -5
- package/dist/proc/limits.d.ts +19 -0
- package/dist/proc/limits.js +19 -0
- package/dist/proc/supervisor.js +8 -1
- package/dist/session/validate-session.js +32 -1
- package/dist/team/runtime.d.ts +8 -0
- package/dist/team/runtime.js +9 -0
- package/dist/team/subagent.d.ts +23 -0
- package/dist/team/subagent.js +14 -3
- package/dist/team/types.d.ts +6 -0
- package/dist/tools/bounded-spawn.d.ts +56 -0
- package/dist/tools/bounded-spawn.js +76 -0
- package/dist/tools/index.d.ts +30 -7
- package/dist/tools/index.js +14 -0
- package/dist/tools/plan-tools.d.ts +14 -2
- package/dist/tools/plan-tools.js +5 -2
- package/dist/tools/search-tools.js +52 -44
- package/dist/ui/App.js +145 -25
- package/dist/ui/BottomStatusRow.js +1 -1
- package/dist/ui/Composer.d.ts +7 -1
- package/dist/ui/Composer.js +9 -6
- package/dist/ui/GovernancePanel.d.ts +50 -0
- package/dist/ui/GovernancePanel.js +77 -0
- package/dist/ui/GovernanceRun.d.ts +18 -0
- package/dist/ui/GovernanceRun.js +11 -0
- package/dist/ui/ScrollbackShell.d.ts +35 -7
- package/dist/ui/ScrollbackShell.js +1 -1
- package/dist/ui/StatusBar.d.ts +15 -2
- package/dist/ui/StatusBar.js +10 -4
- package/dist/ui/Transcript.js +3 -0
- package/dist/ui/entries/GovernanceCard.d.ts +30 -0
- package/dist/ui/entries/GovernanceCard.js +32 -0
- package/dist/ui/governance-copy.d.ts +69 -0
- package/dist/ui/governance-copy.js +104 -0
- package/dist/ui/governance-status.d.ts +145 -0
- package/dist/ui/governance-status.js +326 -0
- package/dist/ui/interaction-copy.d.ts +4 -0
- package/dist/ui/interaction-copy.js +5 -0
- package/dist/ui/interaction-hints.d.ts +17 -0
- package/dist/ui/interaction-hints.js +14 -2
- package/dist/ui/layout/governance-board.d.ts +121 -0
- package/dist/ui/layout/governance-board.js +382 -0
- package/dist/ui/layout/governance-card.d.ts +53 -0
- package/dist/ui/layout/governance-card.js +134 -0
- package/dist/ui/layout/governance-chip.d.ts +38 -0
- package/dist/ui/layout/governance-chip.js +64 -0
- package/dist/ui/layout/governance-panel.d.ts +85 -0
- package/dist/ui/layout/governance-panel.js +358 -0
- package/dist/ui/layout/governance-roster.d.ts +65 -0
- package/dist/ui/layout/governance-roster.js +275 -0
- package/dist/ui/layout/status-detail-layout.js +3 -3
- package/dist/ui/layout/status-layout.d.ts +5 -2
- package/dist/ui/layout/status-layout.js +10 -6
- package/dist/ui/layout/virtual-window.js +11 -0
- package/dist/ui/overlays/GovernanceBoardOverlay.d.ts +41 -0
- package/dist/ui/overlays/GovernanceBoardOverlay.js +76 -0
- package/dist/ui/overlays/GovernanceSettingsPage.d.ts +48 -0
- package/dist/ui/overlays/GovernanceSettingsPage.js +93 -0
- package/dist/ui/overlays/HelpOverlay.js +4 -3
- package/dist/ui/overlays/QuestionOverlay.d.ts +9 -1
- package/dist/ui/overlays/QuestionOverlay.js +4 -2
- package/dist/ui/overlays/SettingsScreen.d.ts +7 -0
- package/dist/ui/overlays/SettingsScreen.js +21 -1
- package/dist/ui/overlays/governance-settings.d.ts +79 -0
- package/dist/ui/overlays/governance-settings.js +140 -0
- package/dist/ui/overlays/model-profile-state.d.ts +6 -1
- package/dist/ui/overlays/model-profile-state.js +10 -0
- package/dist/ui/palettes.d.ts +17 -0
- package/dist/ui/palettes.js +9 -0
- package/dist/ui/scrollback/LiveTail.d.ts +3 -1
- package/dist/ui/scrollback/budget.d.ts +12 -6
- package/dist/ui/scrollback/budget.js +11 -4
- package/dist/ui/scrollback/live-preview.js +2 -0
- package/dist/ui/scrollback/settled-boundary.js +3 -0
- package/dist/ui/scrollback/tail-anchor.d.ts +5 -2
- package/dist/ui/scrollback/tail-anchor.js +3 -1
- package/dist/ui/shortcuts.js +2 -2
- package/dist/ui/theme.d.ts +7 -0
- package/dist/ui/theme.js +1 -0
- package/dist/ui/transcript-text.js +9 -0
- package/dist/ui/use-wheel-routing.js +3 -1
- package/package.json +2 -2
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cooperative pause: `/gov pause` (governance-mode-v1-1 6, V-1..V-4).
|
|
3
|
+
*
|
|
4
|
+
* ASCII ONLY (`governance/` is glyph-scanned).
|
|
5
|
+
*
|
|
6
|
+
* WHAT IT IS. The user asks the run to stop at the next clean boundary: the stage that is
|
|
7
|
+
* running finishes (every submission lands, every GT is booked, nothing is killed or rolled
|
|
8
|
+
* back), and THEN the run becomes `paused`. If no stage is running the run pauses at once.
|
|
9
|
+
* `Esc` is the other, harder control and is unchanged: it aborts the stage and throws away
|
|
10
|
+
* whatever it had not yet committed.
|
|
11
|
+
*
|
|
12
|
+
* WHAT IT IS NOT. It adds no `RunStatus`, no event type and no ledger entry: the pause is the
|
|
13
|
+
* existing `paused` status and the existing `run_paused` event with `reason:
|
|
14
|
+
* 'user_requested'`, so resume, the lock, the panel and the replay all work unchanged (V-1).
|
|
15
|
+
* Pausing never touches the ledger (V-4), and the request itself is a memory-only flag: a
|
|
16
|
+
* process that dies while one is pending restarts into the crash-recovered `paused` state
|
|
17
|
+
* (V-2), never into a run that "paused itself" for a reason nobody remembers.
|
|
18
|
+
*
|
|
19
|
+
* THE TWO REDEEM POINTS. `applyPendingPause` (a stage just ended) and `requestPause` (no
|
|
20
|
+
* stage is active) are the only places a request becomes a pause, so there is no third path
|
|
21
|
+
* for it to leak through (V-3). A run that is `escalated` is NEVER overwritten: an open
|
|
22
|
+
* escalation is more important than a pause and would vanish from the user's view.
|
|
23
|
+
*/
|
|
24
|
+
import type { EngineCore } from './engine-core.js';
|
|
25
|
+
export type PauseOutcome = 'paused_now' | 'pending' | 'already_paused' | 'escalated' | 'no_run';
|
|
26
|
+
/**
|
|
27
|
+
* AGENT-FACING (the Orchestrator reads these as tool results). Each one stands on its own:
|
|
28
|
+
* the orchestrator block has no `PAUSED:` clause, so the sentence itself must say "stop".
|
|
29
|
+
* It also forbids `gov_run resume` because only the person who paused may resume; the
|
|
30
|
+
* digest's usual `NEXT: resume` line is replaced for the same reason.
|
|
31
|
+
*/
|
|
32
|
+
export declare const PAUSED_BY_USER_HEADLINE: string;
|
|
33
|
+
export declare const PAUSE_APPLIED_LINE: string;
|
|
34
|
+
export declare const PAUSE_DROPPED_LINE = "Pause request dropped: the run is already waiting on you (escalated).";
|
|
35
|
+
/**
|
|
36
|
+
* Replace the digest's trailing `NEXT:` block (it is always the last section), optionally
|
|
37
|
+
* putting `above` on the line just before it.
|
|
38
|
+
*/
|
|
39
|
+
export declare function replaceNextBlock(text: string, line: string, above?: string): string;
|
|
40
|
+
/** The refusal `gov_dispatch` returns while a pause is pending or applied. */
|
|
41
|
+
export declare function pausedByUserRefusal(core: EngineCore): string;
|
|
42
|
+
/**
|
|
43
|
+
* Turn the run into `paused`. Idempotent and conservative: anything but `executing` is
|
|
44
|
+
* left exactly as it is (`paused` already is what was asked; `escalated` outranks it).
|
|
45
|
+
* Returns whether this call did the pausing.
|
|
46
|
+
*/
|
|
47
|
+
export declare function settlePause(core: EngineCore, reason: 'user_requested'): Promise<boolean>;
|
|
48
|
+
/**
|
|
49
|
+
* A stage just returned `text`. If the user asked for a pause meanwhile, redeem it now.
|
|
50
|
+
* Always clears the request: whatever the outcome, it has been dealt with.
|
|
51
|
+
*/
|
|
52
|
+
export declare function applyPendingPause(core: EngineCore, text: string): Promise<string>;
|
|
53
|
+
/** `/gov pause`. The caller reports the outcome; this only changes engine state. */
|
|
54
|
+
export declare function requestPause(core: EngineCore): Promise<PauseOutcome>;
|
|
55
|
+
/** `/gov pause cancel`. True when a pending request was actually withdrawn. */
|
|
56
|
+
export declare function cancelPause(core: EngineCore): boolean;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cooperative pause: `/gov pause` (governance-mode-v1-1 6, V-1..V-4).
|
|
3
|
+
*
|
|
4
|
+
* ASCII ONLY (`governance/` is glyph-scanned).
|
|
5
|
+
*
|
|
6
|
+
* WHAT IT IS. The user asks the run to stop at the next clean boundary: the stage that is
|
|
7
|
+
* running finishes (every submission lands, every GT is booked, nothing is killed or rolled
|
|
8
|
+
* back), and THEN the run becomes `paused`. If no stage is running the run pauses at once.
|
|
9
|
+
* `Esc` is the other, harder control and is unchanged: it aborts the stage and throws away
|
|
10
|
+
* whatever it had not yet committed.
|
|
11
|
+
*
|
|
12
|
+
* WHAT IT IS NOT. It adds no `RunStatus`, no event type and no ledger entry: the pause is the
|
|
13
|
+
* existing `paused` status and the existing `run_paused` event with `reason:
|
|
14
|
+
* 'user_requested'`, so resume, the lock, the panel and the replay all work unchanged (V-1).
|
|
15
|
+
* Pausing never touches the ledger (V-4), and the request itself is a memory-only flag: a
|
|
16
|
+
* process that dies while one is pending restarts into the crash-recovered `paused` state
|
|
17
|
+
* (V-2), never into a run that "paused itself" for a reason nobody remembers.
|
|
18
|
+
*
|
|
19
|
+
* THE TWO REDEEM POINTS. `applyPendingPause` (a stage just ended) and `requestPause` (no
|
|
20
|
+
* stage is active) are the only places a request becomes a pause, so there is no third path
|
|
21
|
+
* for it to leak through (V-3). A run that is `escalated` is NEVER overwritten: an open
|
|
22
|
+
* escalation is more important than a pause and would vanish from the user's view.
|
|
23
|
+
*/
|
|
24
|
+
import { composeDigest, USER_PAUSED_NEXT } from './digest.js';
|
|
25
|
+
/**
|
|
26
|
+
* AGENT-FACING (the Orchestrator reads these as tool results). Each one stands on its own:
|
|
27
|
+
* the orchestrator block has no `PAUSED:` clause, so the sentence itself must say "stop".
|
|
28
|
+
* It also forbids `gov_run resume` because only the person who paused may resume; the
|
|
29
|
+
* digest's usual `NEXT: resume` line is replaced for the same reason.
|
|
30
|
+
*/
|
|
31
|
+
export const PAUSED_BY_USER_HEADLINE = 'PAUSED: the user paused this run. Do not dispatch more stages and do not call ' +
|
|
32
|
+
'gov_run resume yourself; tell the user it is paused and wait for them to run /gov resume.';
|
|
33
|
+
export const PAUSE_APPLIED_LINE = "PAUSED: the run is now paused at the user's request after this stage. Do not dispatch " +
|
|
34
|
+
'more stages; tell the user and wait.';
|
|
35
|
+
export const PAUSE_DROPPED_LINE = 'Pause request dropped: the run is already waiting on you (escalated).';
|
|
36
|
+
/**
|
|
37
|
+
* Replace the digest's trailing `NEXT:` block (it is always the last section), optionally
|
|
38
|
+
* putting `above` on the line just before it.
|
|
39
|
+
*/
|
|
40
|
+
export function replaceNextBlock(text, line, above) {
|
|
41
|
+
const at = text.lastIndexOf('\nNEXT:');
|
|
42
|
+
const head = at < 0 ? text : text.slice(0, at);
|
|
43
|
+
const lead = above === undefined ? '' : `\n${above}`;
|
|
44
|
+
return `${head}${lead}\nNEXT:\n ${line}`;
|
|
45
|
+
}
|
|
46
|
+
/** The refusal `gov_dispatch` returns while a pause is pending or applied. */
|
|
47
|
+
export function pausedByUserRefusal(core) {
|
|
48
|
+
const text = composeDigest(core, { headline: PAUSED_BY_USER_HEADLINE });
|
|
49
|
+
return replaceNextBlock(text, USER_PAUSED_NEXT);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Turn the run into `paused`. Idempotent and conservative: anything but `executing` is
|
|
53
|
+
* left exactly as it is (`paused` already is what was asked; `escalated` outranks it).
|
|
54
|
+
* Returns whether this call did the pausing.
|
|
55
|
+
*/
|
|
56
|
+
export async function settlePause(core, reason) {
|
|
57
|
+
const run = core.run;
|
|
58
|
+
core.pauseRequested = false;
|
|
59
|
+
if (run === null || run.status !== 'executing')
|
|
60
|
+
return false;
|
|
61
|
+
run.status = 'paused';
|
|
62
|
+
core.userPaused = true;
|
|
63
|
+
core.log('run_paused', 'engine', { reason });
|
|
64
|
+
core.checkpoint();
|
|
65
|
+
core.emit((snapshot) => ({ type: 'run_paused', snapshot, reason }));
|
|
66
|
+
await core.flush();
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* A stage just returned `text`. If the user asked for a pause meanwhile, redeem it now.
|
|
71
|
+
* Always clears the request: whatever the outcome, it has been dealt with.
|
|
72
|
+
*/
|
|
73
|
+
export async function applyPendingPause(core, text) {
|
|
74
|
+
if (!core.pauseRequested)
|
|
75
|
+
return text;
|
|
76
|
+
const run = core.run;
|
|
77
|
+
if (run !== null && run.status === 'executing' && (await settlePause(core, 'user_requested'))) {
|
|
78
|
+
return replaceNextBlock(text, USER_PAUSED_NEXT, PAUSE_APPLIED_LINE);
|
|
79
|
+
}
|
|
80
|
+
core.pauseRequested = false;
|
|
81
|
+
core.emit((snapshot) => ({ type: 'agent_state', snapshot }));
|
|
82
|
+
return run?.status === 'escalated' ? `${text}\n${PAUSE_DROPPED_LINE}` : text;
|
|
83
|
+
}
|
|
84
|
+
/** `/gov pause`. The caller reports the outcome; this only changes engine state. */
|
|
85
|
+
export async function requestPause(core) {
|
|
86
|
+
const run = core.run;
|
|
87
|
+
if (run === null || run.status === 'closed' || run.status === 'abandoned')
|
|
88
|
+
return 'no_run';
|
|
89
|
+
if (run.status === 'paused')
|
|
90
|
+
return 'already_paused';
|
|
91
|
+
if (run.status === 'escalated')
|
|
92
|
+
return 'escalated';
|
|
93
|
+
if (core.isStageActive()) {
|
|
94
|
+
core.pauseRequested = true;
|
|
95
|
+
core.emit((snapshot) => ({ type: 'agent_state', snapshot }));
|
|
96
|
+
return 'pending';
|
|
97
|
+
}
|
|
98
|
+
return (await settlePause(core, 'user_requested')) ? 'paused_now' : 'already_paused';
|
|
99
|
+
}
|
|
100
|
+
/** `/gov pause cancel`. True when a pending request was actually withdrawn. */
|
|
101
|
+
export function cancelPause(core) {
|
|
102
|
+
if (!core.pauseRequested)
|
|
103
|
+
return false;
|
|
104
|
+
core.pauseRequested = false;
|
|
105
|
+
core.emit((snapshot) => ({ type: 'agent_state', snapshot }));
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Who belongs to a phase, and the small ledger queries the inspector stages share.
|
|
3
|
+
*
|
|
4
|
+
* ASCII ONLY (`governance/` is glyph-scanned).
|
|
5
|
+
*/
|
|
6
|
+
import { hasBooked } from '../rules/ledger-state.js';
|
|
7
|
+
import type { PhaseState, RunState, WorkPackage } from '../types.js';
|
|
8
|
+
import type { EngineCore } from './engine-core.js';
|
|
9
|
+
/** The live (not dropped) work packages of a phase, in plan order. */
|
|
10
|
+
export declare function livePhaseWps(run: RunState, phase: PhaseState): WorkPackage[];
|
|
11
|
+
/** Active scored agents that worked on the phase: its DEVs, REVs and TESTs, deduplicated. */
|
|
12
|
+
export declare function phaseMembers(core: EngineCore, phase: PhaseState): string[];
|
|
13
|
+
/** True when the agent already took an integration root-cause hit (D13) in this phase. */
|
|
14
|
+
export declare function wasPrimaryInPhase(core: EngineCore, agentId: string, phaseIndex: number): boolean;
|
|
15
|
+
export declare function alreadyBooked(core: EngineCore, agentId: string, code: Parameters<typeof hasBooked>[2], incidentId: string): boolean;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Who belongs to a phase, and the small ledger queries the inspector stages share.
|
|
3
|
+
*
|
|
4
|
+
* ASCII ONLY (`governance/` is glyph-scanned).
|
|
5
|
+
*/
|
|
6
|
+
import { hasBooked } from '../rules/ledger-state.js';
|
|
7
|
+
/** The live (not dropped) work packages of a phase, in plan order. */
|
|
8
|
+
export function livePhaseWps(run, phase) {
|
|
9
|
+
return phase.wpIds
|
|
10
|
+
.map((id) => run.workPackages[id])
|
|
11
|
+
.filter((wp) => wp !== undefined && wp.status !== 'dropped');
|
|
12
|
+
}
|
|
13
|
+
/** Active scored agents that worked on the phase: its DEVs, REVs and TESTs, deduplicated. */
|
|
14
|
+
export function phaseMembers(core, phase) {
|
|
15
|
+
const run = core.requireRun();
|
|
16
|
+
const ids = new Set();
|
|
17
|
+
for (const wp of livePhaseWps(run, phase)) {
|
|
18
|
+
for (const id of [wp.assignee, wp.reviewer, wp.tester]) {
|
|
19
|
+
if (id !== null && core.ledger.agents[id]?.status === 'active')
|
|
20
|
+
ids.add(id);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return [...ids];
|
|
24
|
+
}
|
|
25
|
+
/** True when the agent already took an integration root-cause hit (D13) in this phase. */
|
|
26
|
+
export function wasPrimaryInPhase(core, agentId, phaseIndex) {
|
|
27
|
+
const agent = core.ledger.agents[agentId];
|
|
28
|
+
if (agent === undefined)
|
|
29
|
+
return false;
|
|
30
|
+
return agent.history.some((e) => e.code === 'D13' && e.phase === phaseIndex);
|
|
31
|
+
}
|
|
32
|
+
export function alreadyBooked(core, agentId, code, incidentId) {
|
|
33
|
+
return hasBooked(core.ledger, agentId, code, incidentId);
|
|
34
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase planner: validate a work-package graph and layer it into phases
|
|
3
|
+
* (governance-mode section 8.1 "phase layering").
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY (`governance/` is glyph-scanned). Pure: no I/O, no clock, no randomness.
|
|
6
|
+
*
|
|
7
|
+
* Phases are topological layers of `dependsOn`: a WP's phase is one more than the
|
|
8
|
+
* deepest phase among its dependencies, so everything in one phase is independent and
|
|
9
|
+
* runs in parallel. Parallel WPs must not own the same files - two developers editing one
|
|
10
|
+
* file in the same stage is a merge conflict the governance model has no stage to
|
|
11
|
+
* resolve - so same-layer ownership overlap is REJECTED with a message that names both
|
|
12
|
+
* WPs and tells the orchestrator how to fix the plan (move one to a later layer).
|
|
13
|
+
*
|
|
14
|
+
* EVERY failure begins `NOT ACCEPTED:` so the tool layer can return it as plain text
|
|
15
|
+
* (D-15: a rejected plan is a normal reply, not an error result). Messages name where a
|
|
16
|
+
* limit comes from (a config key vs the structural ceiling) so the user knows which knob
|
|
17
|
+
* to turn.
|
|
18
|
+
*/
|
|
19
|
+
import type { ContractInput, WorkPackageInput } from '../types.js';
|
|
20
|
+
export interface PlannerConfig {
|
|
21
|
+
/** `governance.maxWorkPackages` (already clamped by config, re-clamped here). */
|
|
22
|
+
maxWorkPackages: number;
|
|
23
|
+
/** `governance.maxPhases`. */
|
|
24
|
+
maxPhases: number;
|
|
25
|
+
/** `min(team.maxSubagents, hardMax)`; only used to emit the chunking NOTE. */
|
|
26
|
+
teamCeiling?: number;
|
|
27
|
+
}
|
|
28
|
+
export interface PlanInput {
|
|
29
|
+
workPackages: readonly WorkPackageInput[];
|
|
30
|
+
contracts?: readonly ContractInput[];
|
|
31
|
+
config: PlannerConfig;
|
|
32
|
+
}
|
|
33
|
+
export interface PlannedPhase {
|
|
34
|
+
/** 0-based. */
|
|
35
|
+
index: number;
|
|
36
|
+
wpIds: string[];
|
|
37
|
+
}
|
|
38
|
+
export type PlanResult = {
|
|
39
|
+
ok: true;
|
|
40
|
+
phases: PlannedPhase[];
|
|
41
|
+
/** wp id -> 0-based phase index. */
|
|
42
|
+
phaseOf: Record<string, number>;
|
|
43
|
+
notes: string[];
|
|
44
|
+
} | {
|
|
45
|
+
ok: false;
|
|
46
|
+
errors: string[];
|
|
47
|
+
};
|
|
48
|
+
/** First dependency cycle as `a -> b -> a`, or null. Deterministic (input order). */
|
|
49
|
+
export declare function findCycle(wps: readonly WorkPackageInput[]): string[] | null;
|
|
50
|
+
/** True when a path (or glob) plausibly names a test file or test directory. */
|
|
51
|
+
export declare function looksLikeTestPath(path: string): boolean;
|
|
52
|
+
/** Validate and layer a plan. Never throws; failures are `{ ok:false, errors }`. */
|
|
53
|
+
export declare function planPhases(input: PlanInput): PlanResult;
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase planner: validate a work-package graph and layer it into phases
|
|
3
|
+
* (governance-mode section 8.1 "phase layering").
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY (`governance/` is glyph-scanned). Pure: no I/O, no clock, no randomness.
|
|
6
|
+
*
|
|
7
|
+
* Phases are topological layers of `dependsOn`: a WP's phase is one more than the
|
|
8
|
+
* deepest phase among its dependencies, so everything in one phase is independent and
|
|
9
|
+
* runs in parallel. Parallel WPs must not own the same files - two developers editing one
|
|
10
|
+
* file in the same stage is a merge conflict the governance model has no stage to
|
|
11
|
+
* resolve - so same-layer ownership overlap is REJECTED with a message that names both
|
|
12
|
+
* WPs and tells the orchestrator how to fix the plan (move one to a later layer).
|
|
13
|
+
*
|
|
14
|
+
* EVERY failure begins `NOT ACCEPTED:` so the tool layer can return it as plain text
|
|
15
|
+
* (D-15: a rejected plan is a normal reply, not an error result). Messages name where a
|
|
16
|
+
* limit comes from (a config key vs the structural ceiling) so the user knows which knob
|
|
17
|
+
* to turn.
|
|
18
|
+
*/
|
|
19
|
+
import { GOV_LIMITS } from '../limits.js';
|
|
20
|
+
const WP_ID_PATTERN = /^[a-z0-9-]+$/;
|
|
21
|
+
const NOT_ACCEPTED = 'NOT ACCEPTED:';
|
|
22
|
+
function effectiveLimit(configured, hard, key) {
|
|
23
|
+
if (configured > hard) {
|
|
24
|
+
return { value: hard, source: `structural limit (config ${key} was ${configured})` };
|
|
25
|
+
}
|
|
26
|
+
return { value: configured, source: `config ${key}` };
|
|
27
|
+
}
|
|
28
|
+
function reject(errors) {
|
|
29
|
+
const prefixed = errors.map((e) => (e.startsWith(NOT_ACCEPTED) ? e : `${NOT_ACCEPTED} ${e}`));
|
|
30
|
+
return { ok: false, errors: prefixed };
|
|
31
|
+
}
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Step 1: structural validation (ids, references)
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
function validateCount(input) {
|
|
36
|
+
const count = input.workPackages.length;
|
|
37
|
+
if (count === 0)
|
|
38
|
+
return ['at least one work package is required.'];
|
|
39
|
+
const limit = effectiveLimit(input.config.maxWorkPackages, GOV_LIMITS.hardMaxWorkPackages, 'governance.maxWorkPackages');
|
|
40
|
+
if (count > limit.value) {
|
|
41
|
+
return [`${count} work packages exceed the limit of ${limit.value} (${limit.source}).`];
|
|
42
|
+
}
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
function validateIds(wps) {
|
|
46
|
+
const errors = [];
|
|
47
|
+
const seen = new Set();
|
|
48
|
+
for (const wp of wps) {
|
|
49
|
+
const idOk = typeof wp.id === 'string' &&
|
|
50
|
+
wp.id.length >= 1 &&
|
|
51
|
+
wp.id.length <= GOV_LIMITS.wpIdChars &&
|
|
52
|
+
WP_ID_PATTERN.test(wp.id);
|
|
53
|
+
if (!idOk) {
|
|
54
|
+
errors.push(`work package id ${JSON.stringify(wp.id)} is invalid; use 1-${GOV_LIMITS.wpIdChars} ` +
|
|
55
|
+
'characters from [a-z0-9-].');
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (seen.has(wp.id))
|
|
59
|
+
errors.push(`duplicate work package id '${wp.id}'.`);
|
|
60
|
+
seen.add(wp.id);
|
|
61
|
+
}
|
|
62
|
+
return errors;
|
|
63
|
+
}
|
|
64
|
+
function validateDependencies(wps) {
|
|
65
|
+
const ids = new Set(wps.map((wp) => wp.id));
|
|
66
|
+
const errors = [];
|
|
67
|
+
for (const wp of wps) {
|
|
68
|
+
for (const dep of wp.dependsOn ?? []) {
|
|
69
|
+
if (dep === wp.id)
|
|
70
|
+
errors.push(`work package '${wp.id}' depends on itself.`);
|
|
71
|
+
else if (!ids.has(dep)) {
|
|
72
|
+
errors.push(`work package '${wp.id}' depends on unknown work package '${dep}'.`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return errors;
|
|
77
|
+
}
|
|
78
|
+
function validateContracts(wps, contracts) {
|
|
79
|
+
const ids = new Set(wps.map((wp) => wp.id));
|
|
80
|
+
const errors = [];
|
|
81
|
+
for (const contract of contracts) {
|
|
82
|
+
if (!ids.has(contract.producer)) {
|
|
83
|
+
errors.push(`contract '${contract.id}' names unknown producer work package '${contract.producer}'.`);
|
|
84
|
+
}
|
|
85
|
+
for (const consumer of contract.consumers) {
|
|
86
|
+
if (!ids.has(consumer)) {
|
|
87
|
+
errors.push(`contract '${contract.id}' names unknown consumer work package '${consumer}'.`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return errors;
|
|
92
|
+
}
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
// Step 2: cycles and layering
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
/** First dependency cycle as `a -> b -> a`, or null. Deterministic (input order). */
|
|
97
|
+
export function findCycle(wps) {
|
|
98
|
+
const deps = new Map(wps.map((wp) => [wp.id, wp.dependsOn ?? []]));
|
|
99
|
+
const state = new Map();
|
|
100
|
+
const stack = [];
|
|
101
|
+
const visit = (id) => {
|
|
102
|
+
if (state.get(id) === 'done')
|
|
103
|
+
return null;
|
|
104
|
+
if (state.get(id) === 'visiting')
|
|
105
|
+
return [...stack.slice(stack.indexOf(id)), id];
|
|
106
|
+
state.set(id, 'visiting');
|
|
107
|
+
stack.push(id);
|
|
108
|
+
for (const dep of deps.get(id) ?? []) {
|
|
109
|
+
const cycle = visit(dep);
|
|
110
|
+
if (cycle)
|
|
111
|
+
return cycle;
|
|
112
|
+
}
|
|
113
|
+
stack.pop();
|
|
114
|
+
state.set(id, 'done');
|
|
115
|
+
return null;
|
|
116
|
+
};
|
|
117
|
+
for (const wp of wps) {
|
|
118
|
+
const cycle = visit(wp.id);
|
|
119
|
+
if (cycle)
|
|
120
|
+
return cycle;
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
function layerOf(wps) {
|
|
125
|
+
const deps = new Map(wps.map((wp) => [wp.id, wp.dependsOn ?? []]));
|
|
126
|
+
const memo = {};
|
|
127
|
+
const depth = (id) => {
|
|
128
|
+
const cached = memo[id];
|
|
129
|
+
if (cached !== undefined)
|
|
130
|
+
return cached;
|
|
131
|
+
const list = deps.get(id) ?? [];
|
|
132
|
+
const value = list.length === 0 ? 0 : 1 + Math.max(...list.map(depth));
|
|
133
|
+
memo[id] = value;
|
|
134
|
+
return value;
|
|
135
|
+
};
|
|
136
|
+
for (const wp of wps)
|
|
137
|
+
depth(wp.id);
|
|
138
|
+
return memo;
|
|
139
|
+
}
|
|
140
|
+
function normalizePath(raw) {
|
|
141
|
+
return raw
|
|
142
|
+
.trim()
|
|
143
|
+
.replace(/\\/g, '/')
|
|
144
|
+
.replace(/^\.\//, '')
|
|
145
|
+
.replace(/\/{2,}/g, '/');
|
|
146
|
+
}
|
|
147
|
+
function globToRegExp(glob) {
|
|
148
|
+
let out = '';
|
|
149
|
+
for (let i = 0; i < glob.length; i++) {
|
|
150
|
+
const ch = glob[i];
|
|
151
|
+
if (ch === '*' && glob[i + 1] === '*') {
|
|
152
|
+
out += '.*';
|
|
153
|
+
i++;
|
|
154
|
+
}
|
|
155
|
+
else if (ch === '*')
|
|
156
|
+
out += '[^/]*';
|
|
157
|
+
else if (ch === '?')
|
|
158
|
+
out += '[^/]';
|
|
159
|
+
else
|
|
160
|
+
out += ch.replace(/[.+^${}()|[\]\\]/g, '\\$&');
|
|
161
|
+
}
|
|
162
|
+
return new RegExp(`^${out}$`);
|
|
163
|
+
}
|
|
164
|
+
/** Strip trailing `/`, `/**`, `/*` so `src/auth/**` and `src/auth` compare as one scope. */
|
|
165
|
+
function toScope(raw) {
|
|
166
|
+
const display = normalizePath(raw);
|
|
167
|
+
const folded = display.toLowerCase();
|
|
168
|
+
const base = folded.replace(/(\/\*\*?|\/)+$/, '');
|
|
169
|
+
const hasGlob = /[*?]/.test(base);
|
|
170
|
+
return { display, base, hasGlob, matcher: hasGlob ? globToRegExp(base) : null };
|
|
171
|
+
}
|
|
172
|
+
function isSegmentPrefix(prefix, path) {
|
|
173
|
+
return prefix === '' || path === prefix || path.startsWith(`${prefix}/`);
|
|
174
|
+
}
|
|
175
|
+
function literalDirOf(base) {
|
|
176
|
+
const segments = base.split('/');
|
|
177
|
+
const firstGlob = segments.findIndex((s) => /[*?]/.test(s));
|
|
178
|
+
return segments.slice(0, firstGlob < 0 ? segments.length : firstGlob).join('/');
|
|
179
|
+
}
|
|
180
|
+
function scopesOverlap(a, b) {
|
|
181
|
+
if (!a.hasGlob && !b.hasGlob) {
|
|
182
|
+
return isSegmentPrefix(a.base, b.base) || isSegmentPrefix(b.base, a.base);
|
|
183
|
+
}
|
|
184
|
+
if (a.hasGlob && b.hasGlob) {
|
|
185
|
+
const dirA = literalDirOf(a.base);
|
|
186
|
+
const dirB = literalDirOf(b.base);
|
|
187
|
+
return isSegmentPrefix(dirA, dirB) || isSegmentPrefix(dirB, dirA);
|
|
188
|
+
}
|
|
189
|
+
const [glob, literal] = a.hasGlob ? [a, b] : [b, a];
|
|
190
|
+
return ((glob.matcher?.test(literal.base) ?? false) ||
|
|
191
|
+
isSegmentPrefix(literal.base, literalDirOf(glob.base)));
|
|
192
|
+
}
|
|
193
|
+
function findOverlaps(wps, phaseOf) {
|
|
194
|
+
const errors = [];
|
|
195
|
+
for (let i = 0; i < wps.length; i++) {
|
|
196
|
+
for (let j = i + 1; j < wps.length; j++) {
|
|
197
|
+
const a = wps[i];
|
|
198
|
+
const b = wps[j];
|
|
199
|
+
if (phaseOf[a.id] !== phaseOf[b.id])
|
|
200
|
+
continue;
|
|
201
|
+
const clash = firstClash(a.files, b.files);
|
|
202
|
+
if (!clash)
|
|
203
|
+
continue;
|
|
204
|
+
errors.push(`work packages '${a.id}' and '${b.id}' both own '${clash.a}' ` +
|
|
205
|
+
`(overlaps '${clash.b}') in phase ${phaseOf[a.id] + 1}. ` +
|
|
206
|
+
`Move one of them to a later layer (add a dependsOn) or split the file ownership.`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return errors;
|
|
210
|
+
}
|
|
211
|
+
function firstClash(filesA, filesB) {
|
|
212
|
+
const scopesB = filesB.map(toScope);
|
|
213
|
+
for (const rawA of filesA) {
|
|
214
|
+
const scopeA = toScope(rawA);
|
|
215
|
+
const hit = scopesB.find((scopeB) => scopesOverlap(scopeA, scopeB));
|
|
216
|
+
if (hit)
|
|
217
|
+
return { a: scopeA.display, b: hit.display };
|
|
218
|
+
}
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
// Step 4: notes
|
|
223
|
+
// ---------------------------------------------------------------------------
|
|
224
|
+
const TEST_PATH = new RegExp([
|
|
225
|
+
'(^|/)(__tests__|tests?|specs?)(/|$)',
|
|
226
|
+
'\\.(test|spec)\\.[a-z0-9]+$',
|
|
227
|
+
'(^|/)test_[^/]+\\.py$',
|
|
228
|
+
'_test\\.[a-z0-9]+$',
|
|
229
|
+
].join('|'), 'i');
|
|
230
|
+
/** True when a path (or glob) plausibly names a test file or test directory. */
|
|
231
|
+
export function looksLikeTestPath(path) {
|
|
232
|
+
return TEST_PATH.test(normalizePath(path));
|
|
233
|
+
}
|
|
234
|
+
function buildNotes(wps, phases, teamCeiling) {
|
|
235
|
+
const notes = [];
|
|
236
|
+
for (const wp of wps) {
|
|
237
|
+
if (!wp.files.some(looksLikeTestPath)) {
|
|
238
|
+
notes.push(`NOTE: work package '${wp.id}' lists no test path in files; the tester may only write ` +
|
|
239
|
+
'files a work package owns (add its test paths, or ignore this for docs/config-only ' +
|
|
240
|
+
'work).');
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const widest = Math.max(...phases.map((p) => p.wpIds.length));
|
|
244
|
+
if (teamCeiling !== undefined && widest > teamCeiling) {
|
|
245
|
+
notes.push(`NOTE: stages will be dispatched in chunks of ${teamCeiling} (team.maxSubagents); ` +
|
|
246
|
+
`the widest phase has ${widest} work packages.`);
|
|
247
|
+
}
|
|
248
|
+
return notes;
|
|
249
|
+
}
|
|
250
|
+
// ---------------------------------------------------------------------------
|
|
251
|
+
// Entry point
|
|
252
|
+
// ---------------------------------------------------------------------------
|
|
253
|
+
/** Validate and layer a plan. Never throws; failures are `{ ok:false, errors }`. */
|
|
254
|
+
export function planPhases(input) {
|
|
255
|
+
const wps = input.workPackages;
|
|
256
|
+
const early = [...validateCount(input), ...validateIds(wps)];
|
|
257
|
+
if (early.length > 0)
|
|
258
|
+
return reject(early);
|
|
259
|
+
const refs = [...validateDependencies(wps), ...validateContracts(wps, input.contracts ?? [])];
|
|
260
|
+
if (refs.length > 0)
|
|
261
|
+
return reject(refs);
|
|
262
|
+
const cycle = findCycle(wps);
|
|
263
|
+
if (cycle)
|
|
264
|
+
return reject([`dependency cycle: ${cycle.join(' -> ')}`]);
|
|
265
|
+
const phaseOf = layerOf(wps);
|
|
266
|
+
const phaseCount = Math.max(...Object.values(phaseOf)) + 1;
|
|
267
|
+
const phaseLimit = effectiveLimit(input.config.maxPhases, GOV_LIMITS.hardMaxPhases, 'governance.maxPhases');
|
|
268
|
+
if (phaseCount > phaseLimit.value) {
|
|
269
|
+
return reject([
|
|
270
|
+
`the dependency chain needs ${phaseCount} phases, over the limit of ${phaseLimit.value} ` +
|
|
271
|
+
`(${phaseLimit.source}). Flatten the dependencies or merge work packages.`,
|
|
272
|
+
]);
|
|
273
|
+
}
|
|
274
|
+
const overlaps = findOverlaps(wps, phaseOf);
|
|
275
|
+
if (overlaps.length > 0)
|
|
276
|
+
return reject(overlaps);
|
|
277
|
+
const phases = Array.from({ length: phaseCount }, (_, index) => ({
|
|
278
|
+
index,
|
|
279
|
+
wpIds: wps.filter((wp) => phaseOf[wp.id] === index).map((wp) => wp.id),
|
|
280
|
+
}));
|
|
281
|
+
return { ok: true, phases, phaseOf, notes: buildNotes(wps, phases, input.config.teamCeiling) };
|
|
282
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resume / replay helpers (governance-mode 8.4, 12.1).
|
|
3
|
+
*
|
|
4
|
+
* ASCII ONLY (`governance/` is glyph-scanned).
|
|
5
|
+
*
|
|
6
|
+
* WHAT IS REBUILT AND WHAT IS NOT. `events.jsonl` is the truth for the LEDGER: every ledger
|
|
7
|
+
* mutation is one logged op, so the ledger is replayed exactly (I-G2). `run.json` is a
|
|
8
|
+
* checkpoint the engine rewrites after every mutation that changes WP / phase state, so it
|
|
9
|
+
* is normally current; if it is missing or unreadable the run cannot be reconstructed from
|
|
10
|
+
* the log (the log carries ledger ops and audit rows, not the full WP graph) and the caller
|
|
11
|
+
* must say so loudly instead of guessing.
|
|
12
|
+
*/
|
|
13
|
+
import type { ReplayFn } from '../store/run-store.js';
|
|
14
|
+
import type { RunState } from '../types.js';
|
|
15
|
+
/**
|
|
16
|
+
* Put every WP that was mid-stage back to the status it was waiting in, and mark a still
|
|
17
|
+
* `running` stage `interrupted`. WPs that already received their submission were routed on
|
|
18
|
+
* by the adjudication half and are untouched - which is exactly what makes a resume
|
|
19
|
+
* re-dispatch only the WPs that lack a submission. Returns the ids that were reset.
|
|
20
|
+
*/
|
|
21
|
+
export declare function recoverInterruptedStage(run: RunState): string[];
|
|
22
|
+
/**
|
|
23
|
+
* `RunStore.load` replay callback: rebuild the ledger from the op log. The run checkpoint is
|
|
24
|
+
* kept as is (only its `seq` is brought up to the log tail, because the engine stamps every
|
|
25
|
+
* checkpoint with the tail it had when it wrote it).
|
|
26
|
+
*/
|
|
27
|
+
export declare const replayCheckpoints: ReplayFn;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resume / replay helpers (governance-mode 8.4, 12.1).
|
|
3
|
+
*
|
|
4
|
+
* ASCII ONLY (`governance/` is glyph-scanned).
|
|
5
|
+
*
|
|
6
|
+
* WHAT IS REBUILT AND WHAT IS NOT. `events.jsonl` is the truth for the LEDGER: every ledger
|
|
7
|
+
* mutation is one logged op, so the ledger is replayed exactly (I-G2). `run.json` is a
|
|
8
|
+
* checkpoint the engine rewrites after every mutation that changes WP / phase state, so it
|
|
9
|
+
* is normally current; if it is missing or unreadable the run cannot be reconstructed from
|
|
10
|
+
* the log (the log carries ledger ops and audit rows, not the full WP graph) and the caller
|
|
11
|
+
* must say so loudly instead of guessing.
|
|
12
|
+
*/
|
|
13
|
+
import { EngineCore } from './engine-core.js';
|
|
14
|
+
/** Waiting status a WP returns to when its in-flight stage was interrupted. */
|
|
15
|
+
function waitingStatusFor(wp) {
|
|
16
|
+
switch (wp.status) {
|
|
17
|
+
case 'dev_running':
|
|
18
|
+
return wp.reworkCount > 0 ? 'rework' : 'pending';
|
|
19
|
+
case 'review_running':
|
|
20
|
+
return 'awaiting_review';
|
|
21
|
+
case 'test_running':
|
|
22
|
+
return 'awaiting_test';
|
|
23
|
+
default:
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Put every WP that was mid-stage back to the status it was waiting in, and mark a still
|
|
29
|
+
* `running` stage `interrupted`. WPs that already received their submission were routed on
|
|
30
|
+
* by the adjudication half and are untouched - which is exactly what makes a resume
|
|
31
|
+
* re-dispatch only the WPs that lack a submission. Returns the ids that were reset.
|
|
32
|
+
*/
|
|
33
|
+
export function recoverInterruptedStage(run) {
|
|
34
|
+
const reset = [];
|
|
35
|
+
for (const id of run.wpOrder) {
|
|
36
|
+
const wp = run.workPackages[id];
|
|
37
|
+
if (wp === undefined)
|
|
38
|
+
continue;
|
|
39
|
+
const waiting = waitingStatusFor(wp);
|
|
40
|
+
if (waiting === null)
|
|
41
|
+
continue;
|
|
42
|
+
wp.status = waiting;
|
|
43
|
+
reset.push(id);
|
|
44
|
+
}
|
|
45
|
+
if (run.stage !== null && run.stage.status === 'running')
|
|
46
|
+
run.stage.status = 'interrupted';
|
|
47
|
+
return reset;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* `RunStore.load` replay callback: rebuild the ledger from the op log. The run checkpoint is
|
|
51
|
+
* kept as is (only its `seq` is brought up to the log tail, because the engine stamps every
|
|
52
|
+
* checkpoint with the tail it had when it wrote it).
|
|
53
|
+
*/
|
|
54
|
+
export const replayCheckpoints = ({ run, events, tailSeq }) => ({
|
|
55
|
+
ledger: EngineCore.replayLedger(events, tailSeq),
|
|
56
|
+
...(run === null ? {} : { run: { ...run, seq: Math.max(run.seq, tailSeq) } }),
|
|
57
|
+
});
|