@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.
- package/README.md +3 -9
- package/dist/conductor/closeout.d.ts +18 -0
- package/dist/conductor/closeout.js +24 -0
- package/dist/conductor/decide.d.ts +25 -0
- package/dist/conductor/decide.js +104 -0
- package/dist/conductor/index.d.ts +16 -0
- package/dist/conductor/index.js +15 -0
- package/dist/conductor/invoke.d.ts +15 -0
- package/dist/conductor/invoke.js +59 -0
- package/dist/conductor/loop.d.ts +25 -0
- package/dist/conductor/loop.js +236 -0
- package/dist/conductor/sensor.d.ts +12 -0
- package/dist/conductor/sensor.js +150 -0
- package/dist/conductor/snapshot.d.ts +13 -0
- package/dist/conductor/snapshot.js +60 -0
- package/dist/conductor/types.d.ts +248 -0
- package/dist/conductor/types.js +47 -0
- package/dist/conductor/urgency.d.ts +16 -0
- package/dist/conductor/urgency.js +70 -0
- package/dist/config/merge.js +21 -1
- package/dist/config/schema.js +45 -2
- package/dist/config/types.d.ts +22 -0
- package/dist/config/types.js +7 -0
- package/dist/extension/conductor-command.d.ts +23 -0
- package/dist/extension/conductor-command.js +93 -0
- package/dist/extension/factory.js +6 -1
- package/dist/routing/types.d.ts +2 -0
- package/dist/routing/types.js +2 -0
- package/dist/runtime.d.ts +45 -0
- package/dist/runtime.js +62 -0
- package/package.json +4 -2
- package/skills/audit-docs/SKILL.md +6 -5
- package/skills/audit-pr/SKILL.md +1 -1
- package/skills/audit-pr/references/PORTABILITY.md +1 -1
- package/skills/execute-phase/SKILL.md +207 -116
- package/skills/execute-phase/references/DESCOPE.md +22 -18
- package/skills/execute-phase/references/EXECUTION_CONTRACT.md +69 -77
- package/skills/execute-phase/references/FOLDING.md +34 -47
- package/skills/execute-phase/references/PREFLIGHT.md +103 -114
- package/skills/execute-phase/references/PRE_EXECUTION_GATE.md +53 -60
- package/skills/init-workspace/SKILL.md +35 -5
- package/skills/init-workspace/references/BOOTSTRAP_DISCOVERY.md +5 -5
- package/skills/init-workspace/references/BOOTSTRAP_WRITE.md +15 -8
- package/skills/init-workspace/references/PORTABILITY.md +1 -1
- package/skills/init-workspace/references/UPGRADE.md +17 -7
- package/skills/log-session/SKILL.md +4 -3
- package/skills/orchestration-envelope/references/TURN_CONTRACT.md +1 -1
- package/skills/pre-execution-review/references/LEDGERS.md +39 -39
- package/skills/pre-execution-review/references/POLICY.md +1 -0
- package/skills/review-change/SKILL.md +1 -1
- package/skills/review-change/references/PORTABILITY.md +1 -1
- package/skills/unit-lane/SKILL.md +154 -0
- package/skills/unit-lane/references/DESIGN.md +35 -0
- package/skills/unit-lane/references/DOCS.md +36 -0
- package/skills/unit-lane/references/EVIDENCE.md +34 -0
- package/skills/unit-lane/references/IMPLEMENT.md +45 -0
- package/skills/unit-lane/references/PLAN.md +38 -0
- package/skills/unit-lane/references/RELEASE.md +45 -0
- package/skills/unit-lane/references/RESEARCH.md +37 -0
- package/skills/unit-lane/references/REVIEW.md +45 -0
- package/skills/unit-lane/references/STEPS.md +23 -0
- package/skills/unit-lane/references/TESTS.md +32 -0
- package/skills/workflow-status/references/ENVELOPE_FIELDS.md +5 -6
- package/skills/workflow-status/references/SENSOR_CORE.md +16 -20
- package/skills/workflow-status/references/SENSOR_SIGNALS.md +5 -7
- package/skills/design-feature/SKILL.md +0 -231
- package/skills/design-feature/references/INTERVIEW.md +0 -74
- package/skills/design-feature/references/PORTABILITY.md +0 -15
- package/skills/design-feature/references/REPAIR.md +0 -88
- package/skills/design-feature/references/UPSERT_EXAMPLE.md +0 -8
- package/skills/design-feature/references/WRITE_AND_UPSERT.md +0 -115
- package/skills/evidence-grounding/SKILL.md +0 -174
- package/skills/evidence-grounding/references/DELEGATION.md +0 -131
- package/skills/evidence-grounding/references/READINESS.md +0 -130
- package/skills/evidence-grounding/references/ROWS.md +0 -93
- package/skills/execute-phase/references/BATCH_AND_PORTABILITY.md +0 -57
- package/skills/execute-phase/references/CLOSEOUT.md +0 -77
- package/skills/execute-phase/references/HANDOFF.md +0 -21
- package/skills/execute-phase/references/OPPORTUNISTIC_FINDING.md +0 -45
- package/skills/execute-phase/references/UNIT_LOOP.md +0 -88
- package/skills/execute-phase/references/WORKFLOWS_FEATURE.md +0 -37
- package/skills/execute-phase/references/WORKFLOWS_FIX.md +0 -43
- package/skills/execute-phase/references/WORKFLOWS_LEGACY.md +0 -26
- package/skills/execute-phase/references/WORKFLOWS_SMALL_PHASED.md +0 -38
- package/skills/generate-docs/SKILL.md +0 -145
- package/skills/generate-docs/references/ADAPTERS.md +0 -15
- package/skills/generate-docs/references/ADAPTER_DISCOVERY.md +0 -33
- package/skills/generate-docs/references/GENERATION_PROCESS.md +0 -90
- package/skills/implementation-discovery/SKILL.md +0 -209
- package/skills/plan-feature/SKILL.md +0 -208
- package/skills/plan-feature/references/ROUTING.md +0 -155
- package/skills/plan-feature-from-issue/SKILL.md +0 -162
- package/skills/plan-feature-scaffold/SKILL.md +0 -112
- package/skills/plan-feature-scaffold/references/SCAFFOLD_PROCESS.md +0 -122
- package/skills/plan-fix/SKILL.md +0 -161
- package/skills/plan-fix/references/PLANNING_PROCESS.md +0 -148
- package/skills/plan-fix/references/SPEC_CONTRACT.md +0 -22
- package/skills/planning-preflight/SKILL.md +0 -130
- package/skills/replan-findings/SKILL.md +0 -131
- package/skills/replan-findings/references/PHASE_APPEND.md +0 -89
- package/skills/review-plan/SKILL.md +0 -154
- package/skills/review-plan/references/CHECKS.md +0 -111
- package/skills/review-plan/references/ENG-CHECKS.md +0 -30
- package/skills/review-plan/references/OUTPUT.md +0 -166
- package/skills/review-spec/SKILL.md +0 -145
- package/skills/review-spec/references/CHECKS.md +0 -108
- package/skills/review-spec/references/OUTPUT.md +0 -160
- package/skills/ship-roadmap/SKILL.md +0 -120
- package/skills/ship-roadmap/references/ADVANCE.md +0 -117
- package/skills/ship-roadmap/references/AUDIT_AND_MERGE.md +0 -64
- package/skills/ship-roadmap/references/CLOSEOUT_AND_LOG.md +0 -27
- package/skills/ship-roadmap/references/FOUNDING.md +0 -101
- package/skills/ship-roadmap/references/GUARDRAILS.md +0 -56
- package/skills/ship-roadmap/references/MODEL_ROUTING.md +0 -16
- package/skills/ship-roadmap/references/PORTABILITY.md +0 -34
- package/skills/ship-roadmap/references/RECOVERY_AND_SELECTION.md +0 -94
- package/skills/ship-roadmap/references/STOP_CONDITIONS.md +0 -9
- 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
|
+
}
|
package/dist/config/merge.js
CHANGED
|
@@ -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
|
-
|
|
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) {
|