@gtrabanco/pi-agentic-workflow 0.13.3 → 0.15.0

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.
Files changed (118) hide show
  1. package/README.md +3 -9
  2. package/dist/conductor/closeout.d.ts +18 -0
  3. package/dist/conductor/closeout.js +24 -0
  4. package/dist/conductor/decide.d.ts +25 -0
  5. package/dist/conductor/decide.js +104 -0
  6. package/dist/conductor/index.d.ts +16 -0
  7. package/dist/conductor/index.js +15 -0
  8. package/dist/conductor/invoke.d.ts +15 -0
  9. package/dist/conductor/invoke.js +59 -0
  10. package/dist/conductor/loop.d.ts +25 -0
  11. package/dist/conductor/loop.js +236 -0
  12. package/dist/conductor/sensor.d.ts +12 -0
  13. package/dist/conductor/sensor.js +150 -0
  14. package/dist/conductor/snapshot.d.ts +13 -0
  15. package/dist/conductor/snapshot.js +60 -0
  16. package/dist/conductor/types.d.ts +248 -0
  17. package/dist/conductor/types.js +47 -0
  18. package/dist/conductor/urgency.d.ts +16 -0
  19. package/dist/conductor/urgency.js +70 -0
  20. package/dist/config/merge.js +21 -1
  21. package/dist/config/schema.js +45 -2
  22. package/dist/config/types.d.ts +22 -0
  23. package/dist/config/types.js +7 -0
  24. package/dist/extension/conductor-command.d.ts +23 -0
  25. package/dist/extension/conductor-command.js +93 -0
  26. package/dist/extension/factory.js +6 -1
  27. package/dist/routing/types.d.ts +2 -0
  28. package/dist/routing/types.js +2 -0
  29. package/dist/runtime.d.ts +45 -0
  30. package/dist/runtime.js +62 -0
  31. package/package.json +4 -2
  32. package/skills/audit-docs/SKILL.md +6 -5
  33. package/skills/audit-pr/SKILL.md +1 -1
  34. package/skills/audit-pr/references/PORTABILITY.md +1 -1
  35. package/skills/execute-phase/SKILL.md +207 -116
  36. package/skills/execute-phase/references/DESCOPE.md +22 -18
  37. package/skills/execute-phase/references/EXECUTION_CONTRACT.md +69 -77
  38. package/skills/execute-phase/references/FOLDING.md +34 -47
  39. package/skills/execute-phase/references/PREFLIGHT.md +103 -114
  40. package/skills/execute-phase/references/PRE_EXECUTION_GATE.md +53 -60
  41. package/skills/init-workspace/SKILL.md +35 -5
  42. package/skills/init-workspace/references/BOOTSTRAP_DISCOVERY.md +5 -5
  43. package/skills/init-workspace/references/BOOTSTRAP_WRITE.md +15 -8
  44. package/skills/init-workspace/references/PORTABILITY.md +1 -1
  45. package/skills/init-workspace/references/UPGRADE.md +17 -7
  46. package/skills/log-session/SKILL.md +4 -3
  47. package/skills/orchestration-envelope/references/TURN_CONTRACT.md +1 -1
  48. package/skills/pre-execution-review/references/LEDGERS.md +39 -39
  49. package/skills/pre-execution-review/references/POLICY.md +1 -0
  50. package/skills/review-change/SKILL.md +1 -1
  51. package/skills/review-change/references/PORTABILITY.md +1 -1
  52. package/skills/unit-lane/SKILL.md +154 -0
  53. package/skills/unit-lane/references/DESIGN.md +35 -0
  54. package/skills/unit-lane/references/DOCS.md +36 -0
  55. package/skills/unit-lane/references/EVIDENCE.md +34 -0
  56. package/skills/unit-lane/references/IMPLEMENT.md +45 -0
  57. package/skills/unit-lane/references/PLAN.md +38 -0
  58. package/skills/unit-lane/references/RELEASE.md +45 -0
  59. package/skills/unit-lane/references/RESEARCH.md +37 -0
  60. package/skills/unit-lane/references/REVIEW.md +45 -0
  61. package/skills/unit-lane/references/STEPS.md +23 -0
  62. package/skills/unit-lane/references/TESTS.md +32 -0
  63. package/skills/workflow-status/references/ENVELOPE_FIELDS.md +5 -6
  64. package/skills/workflow-status/references/SENSOR_CORE.md +16 -20
  65. package/skills/workflow-status/references/SENSOR_SIGNALS.md +5 -7
  66. package/skills/design-feature/SKILL.md +0 -231
  67. package/skills/design-feature/references/INTERVIEW.md +0 -74
  68. package/skills/design-feature/references/PORTABILITY.md +0 -15
  69. package/skills/design-feature/references/REPAIR.md +0 -88
  70. package/skills/design-feature/references/UPSERT_EXAMPLE.md +0 -8
  71. package/skills/design-feature/references/WRITE_AND_UPSERT.md +0 -115
  72. package/skills/evidence-grounding/SKILL.md +0 -174
  73. package/skills/evidence-grounding/references/DELEGATION.md +0 -131
  74. package/skills/evidence-grounding/references/READINESS.md +0 -130
  75. package/skills/evidence-grounding/references/ROWS.md +0 -93
  76. package/skills/execute-phase/references/BATCH_AND_PORTABILITY.md +0 -57
  77. package/skills/execute-phase/references/CLOSEOUT.md +0 -77
  78. package/skills/execute-phase/references/HANDOFF.md +0 -21
  79. package/skills/execute-phase/references/OPPORTUNISTIC_FINDING.md +0 -45
  80. package/skills/execute-phase/references/UNIT_LOOP.md +0 -88
  81. package/skills/execute-phase/references/WORKFLOWS_FEATURE.md +0 -37
  82. package/skills/execute-phase/references/WORKFLOWS_FIX.md +0 -43
  83. package/skills/execute-phase/references/WORKFLOWS_LEGACY.md +0 -26
  84. package/skills/execute-phase/references/WORKFLOWS_SMALL_PHASED.md +0 -38
  85. package/skills/generate-docs/SKILL.md +0 -145
  86. package/skills/generate-docs/references/ADAPTERS.md +0 -15
  87. package/skills/generate-docs/references/ADAPTER_DISCOVERY.md +0 -33
  88. package/skills/generate-docs/references/GENERATION_PROCESS.md +0 -90
  89. package/skills/implementation-discovery/SKILL.md +0 -209
  90. package/skills/plan-feature/SKILL.md +0 -208
  91. package/skills/plan-feature/references/ROUTING.md +0 -155
  92. package/skills/plan-feature-from-issue/SKILL.md +0 -162
  93. package/skills/plan-feature-scaffold/SKILL.md +0 -112
  94. package/skills/plan-feature-scaffold/references/SCAFFOLD_PROCESS.md +0 -122
  95. package/skills/plan-fix/SKILL.md +0 -161
  96. package/skills/plan-fix/references/PLANNING_PROCESS.md +0 -148
  97. package/skills/plan-fix/references/SPEC_CONTRACT.md +0 -22
  98. package/skills/planning-preflight/SKILL.md +0 -130
  99. package/skills/replan-findings/SKILL.md +0 -131
  100. package/skills/replan-findings/references/PHASE_APPEND.md +0 -89
  101. package/skills/review-plan/SKILL.md +0 -154
  102. package/skills/review-plan/references/CHECKS.md +0 -111
  103. package/skills/review-plan/references/ENG-CHECKS.md +0 -30
  104. package/skills/review-plan/references/OUTPUT.md +0 -166
  105. package/skills/review-spec/SKILL.md +0 -145
  106. package/skills/review-spec/references/CHECKS.md +0 -108
  107. package/skills/review-spec/references/OUTPUT.md +0 -160
  108. package/skills/ship-roadmap/SKILL.md +0 -120
  109. package/skills/ship-roadmap/references/ADVANCE.md +0 -117
  110. package/skills/ship-roadmap/references/AUDIT_AND_MERGE.md +0 -64
  111. package/skills/ship-roadmap/references/CLOSEOUT_AND_LOG.md +0 -27
  112. package/skills/ship-roadmap/references/FOUNDING.md +0 -101
  113. package/skills/ship-roadmap/references/GUARDRAILS.md +0 -56
  114. package/skills/ship-roadmap/references/MODEL_ROUTING.md +0 -16
  115. package/skills/ship-roadmap/references/PORTABILITY.md +0 -34
  116. package/skills/ship-roadmap/references/RECOVERY_AND_SELECTION.md +0 -94
  117. package/skills/ship-roadmap/references/STOP_CONDITIONS.md +0 -9
  118. package/skills/ship-roadmap/references/TERMINAL_REPORT.md +0 -34
@@ -0,0 +1,150 @@
1
+ /**
2
+ * runSensor — spawn the workflow-status sensor, validate its output.
3
+ *
4
+ * Spawns `<bin> scripts/workflow-status.mjs --json-only` in `cwd`,
5
+ * collects stdout, parses JSON, validates against the envelope v2 strict
6
+ * schema, and checks for degradations.
7
+ *
8
+ * Any failure path (spawn error, non-JSON, invalid JSON, schema invalid,
9
+ * degradations present) returns `{ ok: false, refusal: "sensor-degraded", detail }`.
10
+ */
11
+ import { spawn } from "node:child_process";
12
+ import { accessSync, constants } from "node:fs";
13
+ import { validateEnvelope } from "@gtrabanco/agentic-workflow-schema";
14
+ const TIMEOUT_MS = 30_000;
15
+ function coerceEnvelope(raw) {
16
+ return raw;
17
+ }
18
+ function resolveCwd(cwd) {
19
+ try {
20
+ accessSync(cwd, constants.R_OK | constants.X_OK);
21
+ return cwd;
22
+ }
23
+ catch {
24
+ return process.cwd();
25
+ }
26
+ }
27
+ export function runSensor(cwd, bin) {
28
+ return new Promise((resolve) => {
29
+ const resolvedCwd = resolveCwd(cwd);
30
+ const child = spawn(bin, ["scripts/workflow-status.mjs", "--json-only"], {
31
+ cwd: resolvedCwd,
32
+ stdio: ["ignore", "pipe", "pipe"],
33
+ timeout: TIMEOUT_MS,
34
+ });
35
+ let stdout = "";
36
+ child.stdout?.on("data", (chunk) => {
37
+ stdout += chunk.toString();
38
+ });
39
+ const timer = setTimeout(() => {
40
+ child.kill("SIGTERM");
41
+ resolve({
42
+ ok: false,
43
+ refusal: "sensor-degraded",
44
+ detail: "sensor timed out after 30s",
45
+ });
46
+ }, TIMEOUT_MS);
47
+ child.on("error", (_err) => {
48
+ clearTimeout(timer);
49
+ resolve({
50
+ ok: false,
51
+ refusal: "sensor-degraded",
52
+ detail: `spawn error: ${_err.message}`,
53
+ });
54
+ });
55
+ child.on("close", (code) => {
56
+ clearTimeout(timer);
57
+ if (code === null || code >= 128) {
58
+ resolve({
59
+ ok: false,
60
+ refusal: "sensor-degraded",
61
+ detail: `process exited with code ${code ?? "unknown"}`,
62
+ });
63
+ return;
64
+ }
65
+ tryParseAndValidate(stdout, resolve);
66
+ });
67
+ });
68
+ }
69
+ function tryParseAndValidate(raw, resolve) {
70
+ // Parse contract: the real sensor emits bare JSON on stdout
71
+ // (`--json-only` is an accepted no-op); a fenced ```json block is
72
+ // accepted as a defensive fallback (the schema package's own
73
+ // extraction contract for skill turn output).
74
+ const candidate = parseJsonCandidate(raw);
75
+ if (candidate === null) {
76
+ resolve({
77
+ ok: false,
78
+ refusal: "sensor-degraded",
79
+ detail: "no JSON found in sensor output",
80
+ });
81
+ return;
82
+ }
83
+ // Validate the envelope object. The non-strict validator is the consumer
84
+ // contract here: the real sensor emits the documented additive fields
85
+ // (next.reason, next.candidate_count) that the fenced strict parser
86
+ // (skill-turn-output oriented) rejects.
87
+ const v2Result = validateEnvelope(candidate.parsed);
88
+ if (!v2Result.ok) {
89
+ resolve({
90
+ ok: false,
91
+ refusal: "sensor-degraded",
92
+ detail: `schema invalid: ${v2Result.errors.join("; ")}`,
93
+ });
94
+ return;
95
+ }
96
+ const envelope = candidate.parsed;
97
+ // Check for degradations in detail
98
+ const detail = envelope.detail;
99
+ if (detail?.degradations &&
100
+ Array.isArray(detail.degradations) &&
101
+ detail.degradations.length > 0) {
102
+ resolve({
103
+ ok: false,
104
+ refusal: "sensor-degraded",
105
+ detail: `sensor degraded: ${detail.degradations.join(", ")}`,
106
+ });
107
+ return;
108
+ }
109
+ resolve({ ok: true, envelope: coerceEnvelope(envelope) });
110
+ }
111
+ /**
112
+ * Parse sensor output: bare JSON first (the real `--json-only` contract),
113
+ * then the last fenced ```json block (defensive fallback matching the
114
+ * schema package's extraction contract). Returns null when neither parses.
115
+ */
116
+ function parseJsonCandidate(text) {
117
+ const trimmed = text.trim();
118
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
119
+ try {
120
+ return { parsed: JSON.parse(trimmed) };
121
+ }
122
+ catch {
123
+ // fall through to the fenced extraction
124
+ }
125
+ }
126
+ const block = extractLastJsonBlock(text);
127
+ if (block === null)
128
+ return null;
129
+ try {
130
+ return { parsed: JSON.parse(block) };
131
+ }
132
+ catch {
133
+ return null;
134
+ }
135
+ }
136
+ /**
137
+ * Extract the last fenced ```json block from raw text.
138
+ * Matches the envelope parse contract used by the schema package.
139
+ */
140
+ function extractLastJsonBlock(text) {
141
+ const match = text.match(/```json\s*([\s\S]*?)\s*```/g);
142
+ if (!match || match.length === 0)
143
+ return null;
144
+ // Take the last one
145
+ const last = match[match.length - 1];
146
+ return last
147
+ .replace(/^```json\s*/i, "")
148
+ .replace(/\s*```$/, "")
149
+ .trim();
150
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * snapshotFromEnvelope — pure mapping from an Envelope v2 to a WorkflowSnapshot v1.
3
+ *
4
+ * The conductor builds its own lightweight snapshot from the envelope; it does
5
+ * NOT call compileWorkflowSnapshot (that's a separate concern).
6
+ */
7
+ import type { WorkflowSnapshot } from "@gtrabanco/agentic-workflow-schema";
8
+ import type { EnvelopeLike } from "./types.js";
9
+ /**
10
+ * Map an Envelope v2 into a WorkflowSnapshot v1.
11
+ * Pure function — no I/O, no side effects.
12
+ */
13
+ export declare function snapshotFromEnvelope(env: EnvelopeLike): WorkflowSnapshot;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * snapshotFromEnvelope — pure mapping from an Envelope v2 to a WorkflowSnapshot v1.
3
+ *
4
+ * The conductor builds its own lightweight snapshot from the envelope; it does
5
+ * NOT call compileWorkflowSnapshot (that's a separate concern).
6
+ */
7
+ /**
8
+ * Map an Envelope v2 into a WorkflowSnapshot v1.
9
+ * Pure function — no I/O, no side effects.
10
+ */
11
+ export function snapshotFromEnvelope(env) {
12
+ const detail = env.detail;
13
+ const rs = detail?.repository_state;
14
+ const status = String(rs?.status ?? "unknown");
15
+ const sourceRevision = rs?.source_revision ?? env.summary;
16
+ // Map unit
17
+ const unitLike = env.unit;
18
+ const unit = unitLike
19
+ ? {
20
+ kind: unitLike.type === "fix" ? "fix" : "feature",
21
+ id: unitLike.id ?? "",
22
+ status: "",
23
+ }
24
+ : null;
25
+ // Phase: current, total, completed; names not available from envelope
26
+ const phaseRaw = env.phase;
27
+ const phase = {
28
+ current: phaseRaw?.current ?? null,
29
+ total: phaseRaw?.total ?? null,
30
+ completed: phaseRaw?.completed ?? null,
31
+ names: [],
32
+ };
33
+ // Repository: branch from unit.branch, headSha from pr.head_sha or falls back
34
+ const pr = env.pr;
35
+ const branch = unitLike?.branch ?? "";
36
+ const headShaFromPr = pr?.head_sha;
37
+ const headSha = headShaFromPr ?? sourceRevision;
38
+ // dirty is always false from the envelope
39
+ const dirty = false;
40
+ // Detect contradictions from the envelope's repository_state
41
+ const contradictions = status === "contradicted"
42
+ ? [{ field: "repository_state", source: "workflow-status", line: 0, detail: "repository_state is contradicted" }]
43
+ : [];
44
+ // Detect unknowns from the envelope's repository_state
45
+ const unknowns = status === "unknown"
46
+ ? [{ field: "repository_state", reason: "repository_state is unknown" }]
47
+ : [];
48
+ return {
49
+ contract: "agentic-workflow/workflow-snapshot",
50
+ version: 1,
51
+ sourceRevision,
52
+ repository: { branch, headSha, dirty },
53
+ repositoryState: status,
54
+ unit,
55
+ phase,
56
+ provenance: [{ field: "envelope", source: "workflow-status", line: 0 }],
57
+ contradictions,
58
+ unknowns,
59
+ };
60
+ }
@@ -0,0 +1,248 @@
1
+ /**
2
+ * Conductor types — configuration, sensor result, decision, and loop envelope.
3
+ *
4
+ * The conductor is a deterministic loop: sensor -> decide -> invoke -> repeat.
5
+ * Everything is pure data; no I/O lives in this module.
6
+ */
7
+ /**
8
+ * Per-iteration control knobs for the conductor loop.
9
+ * All values are optional in the config file; the exported DEFAULT
10
+ * supplies the shipped defaults.
11
+ */
12
+ export interface ConductorConfig {
13
+ /** Maximum loop iterations before a hard stop. */
14
+ iterationsCap?: number;
15
+ /** Path globs that should not be modified during the loop. */
16
+ sensitivePaths?: readonly string[];
17
+ /** Path globs that require extra scrutiny during the loop. */
18
+ securityPaths?: readonly string[];
19
+ /** Path to the run log file, relative to the project root. */
20
+ runLogPath?: string;
21
+ }
22
+ /**
23
+ * Shipped default conductor configuration.
24
+ * Deeply frozen so that merges always produce new objects.
25
+ */
26
+ export declare const DEFAULT_CONDUCTOR_CONFIG: ConductorConfig;
27
+ /**
28
+ * Merge two advance config files on top of the default.
29
+ * Known keys only — unknown keys are silently dropped.
30
+ * Returns a new object each call.
31
+ */
32
+ export declare function mergeAdvanceConfig(globalFile?: ConductorConfig, projectFile?: ConductorConfig): ConductorConfig;
33
+ /** Result returned by runSensor — success or refusal. */
34
+ export type SensorResult = {
35
+ ok: true;
36
+ envelope: EnvelopeLike;
37
+ } | {
38
+ ok: false;
39
+ refusal: "sensor-degraded";
40
+ detail: string;
41
+ };
42
+ /** Result of mapping a decision to an invocable command string. */
43
+ export type InvocationResult = {
44
+ ok: true;
45
+ invocation: string;
46
+ adversarial?: number;
47
+ } | {
48
+ ok: false;
49
+ refusal: "no-decision-available" | "precondition-uncheckable" | "rendering-failed";
50
+ detail?: string;
51
+ };
52
+ export type UrgencyVerdict = "no-urgent" | "interrupt-now" | "finish-first";
53
+ export interface UrgencyResult {
54
+ verdict: UrgencyVerdict;
55
+ /** Issue number present only when verdict is not "no-urgent". */
56
+ issue?: number;
57
+ reason: string;
58
+ }
59
+ export interface LoopDeps {
60
+ runSensor: (cwd: string, bin: string) => Promise<SensorResult>;
61
+ decide: (input: {
62
+ envelope: EnvelopeLike;
63
+ lastOutcome: OutcomeLike | null;
64
+ lastOutcomeSourceRevision: string | null;
65
+ policy: PolicyLike;
66
+ reviewLoopCycles?: {
67
+ spec?: number;
68
+ plan?: number;
69
+ };
70
+ }) => ActionDecision;
71
+ invocation: (decision: ActionDecision, opts: InvocationOpts) => InvocationResult;
72
+ sendUserMessage: (invocation: string) => Promise<{
73
+ ok: boolean;
74
+ }>;
75
+ gitProbe: () => Promise<{
76
+ porcelain: string;
77
+ ahead: number;
78
+ }>;
79
+ appendRunLog: (line: string) => void;
80
+ config: ConductorConfig;
81
+ attended: boolean;
82
+ judgeUrgency: (envelope: EnvelopeLike) => UrgencyResult;
83
+ parkInFlight: () => Promise<void>;
84
+ /** Working directory the sensor runs in (defaults to process.cwd()). */
85
+ cwd?: string;
86
+ /** Runtime binary for the sensor spawn (defaults to "bun"). */
87
+ runtimeBin?: string;
88
+ /** Command names the invocation mapper accepts (defaults to the lane fallback set). */
89
+ commandNames?: Set<string>;
90
+ /** Decision policy forwarded to the decide step (defaults to the lane fallback). */
91
+ policy?: PolicyLike;
92
+ /** Stop after this many successful invocations (1 = one stage per invocation).
93
+ * Unlimited when omitted (tests drive the full loop). */
94
+ maxInvokes?: number;
95
+ }
96
+ export interface LoopResult {
97
+ banner: string;
98
+ stopCode?: string;
99
+ detail?: string;
100
+ iterations: number;
101
+ }
102
+ export interface InvocationOpts {
103
+ commandNames: Set<string>;
104
+ attended: boolean;
105
+ sensitive?: boolean;
106
+ security?: boolean;
107
+ unitScope?: {
108
+ type: string;
109
+ id: string;
110
+ };
111
+ continuation?: {
112
+ preconditions?: ContinuationPrecondition[];
113
+ };
114
+ }
115
+ export interface ContinuationPrecondition {
116
+ id: string;
117
+ check: string;
118
+ satisfied: boolean;
119
+ }
120
+ export type CloseoutStatus = "clean" | "partial";
121
+ /** Minimal envelope shape as seen by the conductor. */
122
+ export interface EnvelopeLike {
123
+ skill: string;
124
+ state: string;
125
+ summary: string;
126
+ unit: {
127
+ type: string;
128
+ id: string | null;
129
+ issue: number | null;
130
+ branch: string | null;
131
+ } | null;
132
+ phase: {
133
+ current: string | null;
134
+ total: number | null;
135
+ completed: number | null;
136
+ };
137
+ pr: {
138
+ number: number | null;
139
+ url: string | null;
140
+ state: string;
141
+ head_sha: string | null;
142
+ merge_ready: boolean | null;
143
+ ci: string | null;
144
+ };
145
+ gates: {
146
+ verification: string | null;
147
+ review_pending: boolean | null;
148
+ audit_pending: boolean | null;
149
+ };
150
+ findings: {
151
+ fix_now: unknown[];
152
+ issues_filed: number[];
153
+ untriaged: number;
154
+ decisions_recorded: number;
155
+ };
156
+ blockers: unknown[];
157
+ dependencies: {
158
+ unmet: string[];
159
+ build_order: string[];
160
+ };
161
+ recommendations: {
162
+ product_audit: boolean;
163
+ reason: string | null;
164
+ };
165
+ needs_input: {
166
+ question: string;
167
+ options: string[];
168
+ } | null;
169
+ next: {
170
+ recommended: string;
171
+ alternatives: string[];
172
+ tier: string;
173
+ continuation?: {
174
+ argv: string[];
175
+ preconditions: ContinuationPrecondition[];
176
+ convergence: string;
177
+ };
178
+ };
179
+ detail: unknown;
180
+ }
181
+ /** Minimal outcome shape as seen by the conductor. */
182
+ export interface OutcomeLike {
183
+ contract: string;
184
+ version: number;
185
+ skill: string;
186
+ status: string;
187
+ summary: string;
188
+ next: {
189
+ intent: string;
190
+ targets: string[];
191
+ };
192
+ blockers: unknown[];
193
+ questions: unknown[];
194
+ discoveries: unknown[];
195
+ evidence_refs: string[];
196
+ }
197
+ /** Minimal policy shape as seen by decide. */
198
+ export interface PolicyLike {
199
+ allowedIntents: string[];
200
+ forgeWriteAuthorized: boolean;
201
+ }
202
+ /** Minimal action decision shape as returned by decide. */
203
+ export interface ActionDecision {
204
+ kind: "invoke" | "sense" | "stop";
205
+ intent: string;
206
+ targets: string[];
207
+ reasonCode: string;
208
+ evidenceRefs: string[];
209
+ detail: string;
210
+ }
211
+ /** Minimal workflow snapshot shape. */
212
+ export interface WorkflowSnapshotLike {
213
+ contract: string;
214
+ version: number;
215
+ sourceRevision: string;
216
+ repository: {
217
+ branch: string;
218
+ headSha: string;
219
+ dirty: boolean;
220
+ };
221
+ repositoryState: "missing" | "draft" | "frozen" | "contradicted" | "needs-input" | "unknown";
222
+ unit: {
223
+ kind: "feature" | "fix";
224
+ id: string;
225
+ status: string;
226
+ } | null;
227
+ phase: {
228
+ current: string | null;
229
+ total: number | null;
230
+ completed: number | null;
231
+ names: string[];
232
+ };
233
+ provenance: Array<{
234
+ field: string;
235
+ source: string;
236
+ line: number;
237
+ }>;
238
+ contradictions: Array<{
239
+ field: string;
240
+ source: string;
241
+ line: number;
242
+ detail: string;
243
+ }>;
244
+ unknowns: Array<{
245
+ field: string;
246
+ reason: string;
247
+ }>;
248
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Conductor types — configuration, sensor result, decision, and loop envelope.
3
+ *
4
+ * The conductor is a deterministic loop: sensor -> decide -> invoke -> repeat.
5
+ * Everything is pure data; no I/O lives in this module.
6
+ */
7
+ /**
8
+ * Shipped default conductor configuration.
9
+ * Deeply frozen so that merges always produce new objects.
10
+ */
11
+ export const DEFAULT_CONDUCTOR_CONFIG = Object.freeze({
12
+ iterationsCap: 12,
13
+ sensitivePaths: Object.freeze([]),
14
+ securityPaths: Object.freeze([]),
15
+ runLogPath: ".agentic-workflow/advance-run.log",
16
+ });
17
+ /**
18
+ * Merge two advance config files on top of the default.
19
+ * Known keys only — unknown keys are silently dropped.
20
+ * Returns a new object each call.
21
+ */
22
+ export function mergeAdvanceConfig(globalFile = {}, projectFile = {}) {
23
+ const result = {
24
+ iterationsCap: DEFAULT_CONDUCTOR_CONFIG.iterationsCap,
25
+ sensitivePaths: globalFile.sensitivePaths?.slice() ?? [],
26
+ securityPaths: globalFile.securityPaths?.slice() ?? [],
27
+ runLogPath: DEFAULT_CONDUCTOR_CONFIG.runLogPath,
28
+ };
29
+ if (projectFile.iterationsCap !== undefined) {
30
+ result.iterationsCap = projectFile.iterationsCap;
31
+ }
32
+ if (projectFile.sensitivePaths !== undefined) {
33
+ result.sensitivePaths = projectFile.sensitivePaths.slice();
34
+ }
35
+ if (projectFile.securityPaths !== undefined) {
36
+ result.securityPaths = projectFile.securityPaths.slice();
37
+ }
38
+ if (projectFile.runLogPath !== undefined) {
39
+ result.runLogPath = projectFile.runLogPath;
40
+ }
41
+ return Object.freeze({
42
+ iterationsCap: result.iterationsCap,
43
+ sensitivePaths: Object.freeze(result.sensitivePaths ?? []),
44
+ securityPaths: Object.freeze(result.securityPaths ?? []),
45
+ runLogPath: result.runLogPath,
46
+ });
47
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * judgeUrgency > classify urgency from an envelope's urgent detail.
3
+ *
4
+ * Implements the five-row urgency table:
5
+ * 1. no urgent issues → no-urgent
6
+ * 2. fix-next label present → finish-first (queue head, never interrupt)
7
+ * 3. interruptibility.dirty === false → interrupt-now
8
+ * 4. tasks_from_boundary ≤ 1 → finish-first
9
+ * 5. else → finish-first (fail-safe: finish first, never interrupt mid-phase)
10
+ */
11
+ import type { EnvelopeLike, UrgencyResult } from "./types.js";
12
+ /**
13
+ * Classify urgency from an envelope's urgent detail.
14
+ * Pure function > no I/O, no side effects.
15
+ */
16
+ export declare function judgeUrgency(env: EnvelopeLike): UrgencyResult;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * judgeUrgency > classify urgency from an envelope's urgent detail.
3
+ *
4
+ * Implements the five-row urgency table:
5
+ * 1. no urgent issues → no-urgent
6
+ * 2. fix-next label present → finish-first (queue head, never interrupt)
7
+ * 3. interruptibility.dirty === false → interrupt-now
8
+ * 4. tasks_from_boundary ≤ 1 → finish-first
9
+ * 5. else → finish-first (fail-safe: finish first, never interrupt mid-phase)
10
+ */
11
+ /**
12
+ * Classify urgency from an envelope's urgent detail.
13
+ * Pure function > no I/O, no side effects.
14
+ */
15
+ export function judgeUrgency(env) {
16
+ const detail = env.detail;
17
+ const urgent = detail?.urgent;
18
+ if (!urgent) {
19
+ return { verdict: "no-urgent", reason: "no urgent detail found" };
20
+ }
21
+ const issues = urgent.issues ?? [];
22
+ const interruptibility = urgent.interruptibility;
23
+ // Row 1: no urgent issues → no-urgent
24
+ if (issues.length === 0) {
25
+ return { verdict: "no-urgent", reason: "no urgent issues present" };
26
+ }
27
+ // Pick the first issue (highest priority)
28
+ const firstIssue = issues[0];
29
+ const issueNumber = firstIssue.number;
30
+ // Row 2: fix-next label → finish-first (queue head, never interrupt)
31
+ const hasFixNext = issues.some((i) => i.label === "fix-next");
32
+ if (hasFixNext) {
33
+ return {
34
+ verdict: "finish-first",
35
+ issue: issueNumber,
36
+ reason: "fix-next issue in queue: finish current unit first, queue head priority",
37
+ };
38
+ }
39
+ // Fall through to interruptibility checks
40
+ if (interruptibility) {
41
+ // Row 3: dirty === false → interrupt-now
42
+ if (!interruptibility.dirty) {
43
+ return {
44
+ verdict: "interrupt-now",
45
+ issue: issueNumber,
46
+ reason: `unit ${interruptibility.unit} is not dirty > safe to interrupt`,
47
+ };
48
+ }
49
+ // Row 4: tasks_from_boundary ≤ 1 → finish-first
50
+ if (interruptibility.tasks_from_boundary <= 1) {
51
+ return {
52
+ verdict: "finish-first",
53
+ issue: issueNumber,
54
+ reason: "few tasks remaining (≤ 1) > finish first",
55
+ };
56
+ }
57
+ // Row 5: fail-safe finish-first (dirty, tasks > 1, no fix-next)
58
+ return {
59
+ verdict: "finish-first",
60
+ issue: issueNumber,
61
+ reason: "fail-safe: unit is dirty with multiple tasks remaining > finish first, never interrupt mid-phase",
62
+ };
63
+ }
64
+ // No interruptibility data → finish-first (fail-safe)
65
+ return {
66
+ verdict: "finish-first",
67
+ issue: issueNumber,
68
+ reason: "no interruptibility data available > finish first (fail-safe)",
69
+ };
70
+ }
@@ -1,5 +1,6 @@
1
1
  import { DEFAULT_CONFIG, DEFAULT_ROUTE } from "./defaults.js";
2
2
  import { SHIPPED_PATH_POLICY, intersectPathPolicy, mergePathProtectionOverrides } from "./path-policy.js";
3
+ import { DEFAULT_ADVANCE_CONFIG } from "./types.js";
3
4
  /**
4
5
  * Project-over-global merge (SPEC S5, S6).
5
6
  *
@@ -32,13 +33,32 @@ export function mergeConfigs(globalFile = {}, projectFile = {}) {
32
33
  for (const name of new Set([...Object.keys(globalCommands), ...Object.keys(projectCommands)])) {
33
34
  commands[name] = resolveRoute(effectiveDefault, globalCommands[name], projectCommands[name]);
34
35
  }
35
- return {
36
+ const merged = {
36
37
  default: effectiveDefault,
37
38
  commands,
38
39
  onUnavailableRoute: projectFile.onUnavailableRoute ?? globalFile.onUnavailableRoute ?? DEFAULT_CONFIG.onUnavailableRoute,
39
40
  onSettle: projectFile.onSettle ?? globalFile.onSettle ?? DEFAULT_CONFIG.onSettle,
40
41
  pathProtection: intersectPathPolicy(SHIPPED_PATH_POLICY, mergePathProtectionOverrides(globalFile.pathProtection, projectFile.pathProtection)),
41
42
  };
43
+ // Advance knobs appear only when a scope declares them, so configs that
44
+ // predate feature 62 keep their exact resolved shape (no undefined key).
45
+ const advance = mergeAdvance(globalFile.advance, projectFile.advance);
46
+ if (advance !== undefined)
47
+ merged.advance = advance;
48
+ return merged;
49
+ }
50
+ /** Project-over-global merge of the advance (conductor) knobs (feature 62).
51
+ * Absent from the result when neither scope declares any — the resolved
52
+ * shape of pre-62 configs is byte-stable. */
53
+ function mergeAdvance(global, project) {
54
+ if (global === undefined && project === undefined)
55
+ return undefined;
56
+ return {
57
+ iterationsCap: pick(DEFAULT_ADVANCE_CONFIG.iterationsCap, project?.iterationsCap, global?.iterationsCap),
58
+ sensitivePaths: project?.sensitivePaths ?? global?.sensitivePaths ?? [...DEFAULT_ADVANCE_CONFIG.sensitivePaths],
59
+ securityPaths: project?.securityPaths ?? global?.securityPaths ?? [...DEFAULT_ADVANCE_CONFIG.securityPaths],
60
+ runLogPath: pick(DEFAULT_ADVANCE_CONFIG.runLogPath, project?.runLogPath, global?.runLogPath),
61
+ };
42
62
  }
43
63
  /** The route a command runs under: its own resolved override, else the default route. */
44
64
  export function effectiveRoute(config, command) {