@bradheitmann/odin-sentinel 0.4.12 → 0.5.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/.claude-plugin/marketplace.json +1 -1
- package/README.md +24 -17
- package/dist/src/harness-pacing/index.d.ts +10 -0
- package/dist/src/harness-pacing/index.js +11 -0
- package/dist/src/harness-pacing/index.js.map +1 -0
- package/dist/src/harness-pacing/recommend.d.ts +28 -0
- package/dist/src/harness-pacing/recommend.js +74 -0
- package/dist/src/harness-pacing/recommend.js.map +1 -0
- package/dist/src/harness-pacing/schema.d.ts +28 -0
- package/dist/src/harness-pacing/schema.js +2 -0
- package/dist/src/harness-pacing/schema.js.map +1 -0
- package/dist/src/harness-pacing/storage.d.ts +32 -0
- package/dist/src/harness-pacing/storage.js +74 -0
- package/dist/src/harness-pacing/storage.js.map +1 -0
- package/dist/src/mcp/server.js +29 -2
- package/dist/src/mcp/server.js.map +1 -1
- package/dist/src/odin-watch/backends/cmux.d.ts +6 -0
- package/dist/src/odin-watch/backends/cmux.js +39 -0
- package/dist/src/odin-watch/backends/cmux.js.map +1 -0
- package/dist/src/odin-watch/backends/tmux.d.ts +6 -0
- package/dist/src/odin-watch/backends/tmux.js +40 -0
- package/dist/src/odin-watch/backends/tmux.js.map +1 -0
- package/dist/src/odin-watch/classifier.d.ts +27 -0
- package/dist/src/odin-watch/classifier.js +182 -0
- package/dist/src/odin-watch/classifier.js.map +1 -0
- package/dist/src/odin-watch/index.d.ts +2 -0
- package/dist/src/odin-watch/index.js +200 -0
- package/dist/src/odin-watch/index.js.map +1 -0
- package/dist/src/odin-watch/snapshotter.d.ts +11 -0
- package/dist/src/odin-watch/snapshotter.js +2 -0
- package/dist/src/odin-watch/snapshotter.js.map +1 -0
- package/dist/src/odin-watch/writers.d.ts +8 -0
- package/dist/src/odin-watch/writers.js +27 -0
- package/dist/src/odin-watch/writers.js.map +1 -0
- package/dist/src/protocol/index.d.ts +3 -1
- package/dist/src/protocol/index.js +4 -1
- package/dist/src/protocol/index.js.map +1 -1
- package/dist/src/protocol/repository.d.ts +14 -0
- package/dist/src/protocol/repository.js +25 -1
- package/dist/src/protocol/repository.js.map +1 -1
- package/dist/src/protocol/schemas.d.ts +144 -0
- package/dist/src/protocol/schemas.js +23 -0
- package/dist/src/protocol/schemas.js.map +1 -1
- package/dist/src/protocol/service.d.ts +19 -2
- package/dist/src/protocol/service.js +89 -3
- package/dist/src/protocol/service.js.map +1 -1
- package/dist/src/protocol/surface-layout.d.ts +20 -0
- package/dist/src/protocol/surface-layout.js +20 -0
- package/dist/src/protocol/surface-layout.js.map +1 -1
- package/dist/src/protocol/version.d.ts +2 -2
- package/dist/src/protocol/version.js +2 -2
- package/dist/src/protocol/version.js.map +1 -1
- package/dist/src/utils/execFileNoThrow.d.ts +5 -0
- package/dist/src/utils/execFileNoThrow.js +18 -0
- package/dist/src/utils/execFileNoThrow.js.map +1 -0
- package/docs/adapters/cmux-adapter.md +168 -0
- package/docs/adapters/herdr-adapter.md +150 -0
- package/docs/adapters/minimux-adapter.md +152 -0
- package/docs/adapters/plain-terminal.md +80 -0
- package/docs/adapters/tmux-adapter.md +150 -0
- package/docs/guides/quick-start.md +7 -7
- package/docs/guides/quickstart-prompts.md +4 -4
- package/docs/lattice/odin-lattice-design.md +555 -0
- package/docs/reference/distribution.md +11 -5
- package/docs/reference/public-surface-audit.md +3 -3
- package/package.json +7 -5
- package/plugins/odin-scp/.claude-plugin/plugin.json +2 -2
- package/plugins/odin-scp/README.md +6 -6
- package/plugins/odin-scp/skills/odin-scp/CHANGELOG.md +12 -0
- package/plugins/odin-scp/skills/odin-scp/SKILL.md +196 -3
- package/plugins/odin-scp/skills/odin-scp/references/canonical-introduction-prompt.md +0 -2
- package/protocol/SCP.md +2 -2
- package/protocol/bootstrap-skill.md +196 -3
- package/protocol/closeout.yaml +1 -1
- package/protocol/delegation.yaml +1 -1
- package/protocol/mission-frontrun/droids-scrutiny-feature-reviewer.md +70 -0
- package/protocol/mission-frontrun/orchestrator-contract.md +70 -0
- package/protocol/mission-frontrun/scrutiny-feature-reviewer-contract.md +73 -0
- package/protocol/mission-frontrun/scrutiny-validator-contract.md +77 -0
- package/protocol/mission-frontrun/worker-contract.md +66 -0
- package/protocol/model-profiles.yaml +8 -1
- package/protocol/receipts/boot-receipt.yaml +13 -0
- package/protocol/role-cards/dev-worker.md +74 -0
- package/protocol/role-cards/exec-asst.md +83 -0
- package/protocol/role-cards/exec-pm.md +66 -0
- package/protocol/role-cards/qa-worker.md +71 -0
- package/protocol/role-cards/team-pm.md +67 -0
- package/protocol/roles.yaml +1 -1
- package/protocol/skill-references/canonical-introduction-prompt.md +0 -2
- package/protocol/topology.yaml +1 -1
- package/scripts/audit/public-surface.mjs +27 -2
- package/scripts/audit/verify-pack.mjs +121 -5
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { execFileNoThrow } from "../../utils/execFileNoThrow.js";
|
|
5
|
+
export class TmuxBackend {
|
|
6
|
+
archiveDir;
|
|
7
|
+
constructor(archiveDir) {
|
|
8
|
+
this.archiveDir = archiveDir;
|
|
9
|
+
}
|
|
10
|
+
async capture(pane_id) {
|
|
11
|
+
const result = await execFileNoThrow("tmux", [
|
|
12
|
+
"capture-pane",
|
|
13
|
+
"-p",
|
|
14
|
+
"-t",
|
|
15
|
+
pane_id,
|
|
16
|
+
]);
|
|
17
|
+
const text = result.status === 0 ? result.stdout : "";
|
|
18
|
+
const ts = Date.now();
|
|
19
|
+
const hash = createHash("sha256").update(text).digest("hex");
|
|
20
|
+
// Archive raw screen text
|
|
21
|
+
try {
|
|
22
|
+
mkdirSync(this.archiveDir, { recursive: true });
|
|
23
|
+
const filename = `${pane_id.replace(/[^a-zA-Z0-9_-]/g, "_")}_${ts}.txt`;
|
|
24
|
+
writeFileSync(join(this.archiveDir, filename), text, {
|
|
25
|
+
encoding: "utf8",
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// Archive failure is non-fatal; snapshot still returned
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
pane_id,
|
|
33
|
+
substrate: "tmux",
|
|
34
|
+
text,
|
|
35
|
+
hash,
|
|
36
|
+
ts,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=tmux.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tmux.js","sourceRoot":"","sources":["../../../../src/odin-watch/backends/tmux.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAGjE,MAAM,OAAO,WAAW;IACL,UAAU,CAAS;IAEpC,YAAY,UAAkB;QAC5B,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,OAAe;QAC3B,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,MAAM,EAAE;YAC3C,cAAc;YACd,IAAI;YACJ,IAAI;YACJ,OAAO;SACR,CAAC,CAAC;QAEH,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;QACtD,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACtB,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAE7D,0BAA0B;QAC1B,IAAI,CAAC;YACH,SAAS,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAChD,MAAM,QAAQ,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC;YACxE,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,EAAE,IAAI,EAAE;gBACnD,QAAQ,EAAE,MAAM;aACjB,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,wDAAwD;QAC1D,CAAC;QAED,OAAO;YACL,OAAO;YACP,SAAS,EAAE,MAAM;YACjB,IAAI;YACJ,IAAI;YACJ,EAAE;SACH,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { WakeVerdict, PromptBudgetClass } from "../protocol/schemas.js";
|
|
2
|
+
export interface ClassifierInput {
|
|
3
|
+
text: string;
|
|
4
|
+
hash: string;
|
|
5
|
+
prev_hash: string | null;
|
|
6
|
+
elapsed_ms: number;
|
|
7
|
+
now_ms: number;
|
|
8
|
+
mandatory_audit_interval_ms: number;
|
|
9
|
+
stale_threshold_ms: number;
|
|
10
|
+
write_scope: string[];
|
|
11
|
+
dirty_paths: string[];
|
|
12
|
+
last_verdict: WakeVerdict | null;
|
|
13
|
+
last_mandatory_audit_ts: number | null;
|
|
14
|
+
}
|
|
15
|
+
export interface ClassifierResult {
|
|
16
|
+
verdict: WakeVerdict;
|
|
17
|
+
wake: 0 | 1;
|
|
18
|
+
reason_codes: string[];
|
|
19
|
+
prompt_budget_class: PromptBudgetClass;
|
|
20
|
+
dirty_out_of_scope: string[];
|
|
21
|
+
last_marker: string | undefined;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Classify a screen snapshot using 8 ordered rules (first-match wins).
|
|
25
|
+
* Pure function — no I/O.
|
|
26
|
+
*/
|
|
27
|
+
export declare function classify(input: ClassifierInput): ClassifierResult;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// Approval markers: exact strings and regex for numbered interactive options
|
|
2
|
+
const APPROVAL_STRINGS = [
|
|
3
|
+
"Proceed with the proposal",
|
|
4
|
+
"Proceed with comment",
|
|
5
|
+
"No and explain why",
|
|
6
|
+
"Enter Select",
|
|
7
|
+
];
|
|
8
|
+
const NUMBERED_OPTION_RE = /^\d+\.\s/m;
|
|
9
|
+
// Crash markers
|
|
10
|
+
const CRASH_STRINGS = [
|
|
11
|
+
"panic",
|
|
12
|
+
"stack trace",
|
|
13
|
+
"program experienced a panic",
|
|
14
|
+
"TUI run error",
|
|
15
|
+
"fatal error",
|
|
16
|
+
];
|
|
17
|
+
// Completion markers (order matters: QA_COMPLETE check before DEV_COMPLETE)
|
|
18
|
+
const QA_COMPLETE_STRINGS = ["QA COMPLETE", "QA_COMPLETE"];
|
|
19
|
+
const DEV_COMPLETE_STRINGS = [
|
|
20
|
+
"DEV_COMPLETE_QA_PENDING",
|
|
21
|
+
"implementation complete",
|
|
22
|
+
"ready for QA",
|
|
23
|
+
];
|
|
24
|
+
// Blocker markers
|
|
25
|
+
const BLOCKER_STRINGS = [
|
|
26
|
+
"BLOCKED",
|
|
27
|
+
"cannot proceed",
|
|
28
|
+
"missing",
|
|
29
|
+
"permission denied",
|
|
30
|
+
"need approval",
|
|
31
|
+
"SPEC-AMENDMENT",
|
|
32
|
+
"non-fast-forward",
|
|
33
|
+
];
|
|
34
|
+
// Working state positive evidence: active spinner chars or streaming progress patterns
|
|
35
|
+
const WORKING_RE = /[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]|(?:\.\.\.|Running|Generating|Compiling|Building|Installing|Fetching|Downloading|Uploading|Processing|Streaming|Writing|Thinking)/;
|
|
36
|
+
function containsAny(text, markers) {
|
|
37
|
+
for (const marker of markers) {
|
|
38
|
+
if (text.includes(marker))
|
|
39
|
+
return marker;
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Classify a screen snapshot using 8 ordered rules (first-match wins).
|
|
45
|
+
* Pure function — no I/O.
|
|
46
|
+
*/
|
|
47
|
+
export function classify(input) {
|
|
48
|
+
const { text, hash, prev_hash, elapsed_ms, now_ms, mandatory_audit_interval_ms, stale_threshold_ms, write_scope, dirty_paths, last_verdict, last_mandatory_audit_ts, } = input;
|
|
49
|
+
const dirty_out_of_scope = [];
|
|
50
|
+
// Compute out-of-scope dirty paths (used in rule 6)
|
|
51
|
+
if (write_scope.length > 0) {
|
|
52
|
+
for (const path of dirty_paths) {
|
|
53
|
+
const inScope = write_scope.some((scope) => path === scope || path.startsWith(scope + "/") || path.startsWith(scope));
|
|
54
|
+
if (!inScope) {
|
|
55
|
+
dirty_out_of_scope.push(path);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Rule 1: Mandatory audit due
|
|
60
|
+
// elapsed since last mandatory audit >= interval → wake, preserve current verdict, budget=compact
|
|
61
|
+
const auditElapsed = last_mandatory_audit_ts !== null
|
|
62
|
+
? now_ms - last_mandatory_audit_ts
|
|
63
|
+
: elapsed_ms;
|
|
64
|
+
if (auditElapsed >= mandatory_audit_interval_ms) {
|
|
65
|
+
const preservedVerdict = last_verdict ?? "UNKNOWN_NEEDS_READ";
|
|
66
|
+
return {
|
|
67
|
+
verdict: preservedVerdict,
|
|
68
|
+
wake: 1,
|
|
69
|
+
reason_codes: ["MANDATORY_AUDIT interval elapsed"],
|
|
70
|
+
prompt_budget_class: "compact",
|
|
71
|
+
dirty_out_of_scope,
|
|
72
|
+
last_marker: undefined,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
// Rule 2: Approval markers
|
|
76
|
+
const approvalMarker = containsAny(text, APPROVAL_STRINGS);
|
|
77
|
+
if (approvalMarker !== null || NUMBERED_OPTION_RE.test(text)) {
|
|
78
|
+
const marker = approvalMarker ?? "numbered interactive option";
|
|
79
|
+
return {
|
|
80
|
+
verdict: "WAITING_APPROVAL",
|
|
81
|
+
wake: 1,
|
|
82
|
+
reason_codes: ["APPROVAL_MARKER"],
|
|
83
|
+
prompt_budget_class: "diagnostic",
|
|
84
|
+
dirty_out_of_scope,
|
|
85
|
+
last_marker: marker,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// Rule 3: Crash markers
|
|
89
|
+
const crashMarker = containsAny(text, CRASH_STRINGS);
|
|
90
|
+
if (crashMarker !== null) {
|
|
91
|
+
return {
|
|
92
|
+
verdict: "CRASHED",
|
|
93
|
+
wake: 1,
|
|
94
|
+
reason_codes: ["CRASH_MARKER"],
|
|
95
|
+
prompt_budget_class: "forensic",
|
|
96
|
+
dirty_out_of_scope,
|
|
97
|
+
last_marker: crashMarker,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
// Rule 4: Completion markers (QA_COMPLETE checked before DEV_COMPLETE)
|
|
101
|
+
const qaMarker = containsAny(text, QA_COMPLETE_STRINGS);
|
|
102
|
+
if (qaMarker !== null) {
|
|
103
|
+
return {
|
|
104
|
+
verdict: "QA_COMPLETE",
|
|
105
|
+
wake: 1,
|
|
106
|
+
reason_codes: ["COMPLETION_MARKER"],
|
|
107
|
+
prompt_budget_class: "compact",
|
|
108
|
+
dirty_out_of_scope,
|
|
109
|
+
last_marker: qaMarker,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
const devMarker = containsAny(text, DEV_COMPLETE_STRINGS);
|
|
113
|
+
if (devMarker !== null) {
|
|
114
|
+
return {
|
|
115
|
+
verdict: "DEV_COMPLETE_QA_PENDING",
|
|
116
|
+
wake: 1,
|
|
117
|
+
reason_codes: ["COMPLETION_MARKER"],
|
|
118
|
+
prompt_budget_class: "compact",
|
|
119
|
+
dirty_out_of_scope,
|
|
120
|
+
last_marker: devMarker,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
// Rule 5: Blocker markers
|
|
124
|
+
const blockerMarker = containsAny(text, BLOCKER_STRINGS);
|
|
125
|
+
if (blockerMarker !== null) {
|
|
126
|
+
return {
|
|
127
|
+
verdict: "BLOCKED",
|
|
128
|
+
wake: 1,
|
|
129
|
+
reason_codes: ["BLOCKER_MARKER"],
|
|
130
|
+
prompt_budget_class: "diagnostic",
|
|
131
|
+
dirty_out_of_scope,
|
|
132
|
+
last_marker: blockerMarker,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
// Rule 6: Scope violation (dirty paths outside write_scope)
|
|
136
|
+
if (dirty_out_of_scope.length > 0) {
|
|
137
|
+
return {
|
|
138
|
+
verdict: "BLOCKED",
|
|
139
|
+
wake: 1,
|
|
140
|
+
reason_codes: dirty_out_of_scope.map((p) => `DIRTY_OUT_OF_SCOPE ${p}`),
|
|
141
|
+
prompt_budget_class: "diagnostic",
|
|
142
|
+
dirty_out_of_scope,
|
|
143
|
+
last_marker: undefined,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
// Rule 7: Stale hash (unchanged content beyond stale threshold)
|
|
147
|
+
if (prev_hash !== null &&
|
|
148
|
+
prev_hash === hash &&
|
|
149
|
+
elapsed_ms > stale_threshold_ms) {
|
|
150
|
+
return {
|
|
151
|
+
verdict: "IDLE",
|
|
152
|
+
wake: 1,
|
|
153
|
+
reason_codes: ["STALE_HASH"],
|
|
154
|
+
prompt_budget_class: "compact",
|
|
155
|
+
dirty_out_of_scope,
|
|
156
|
+
last_marker: undefined,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
// Rule 8: All clear — WORKING requires positive evidence
|
|
160
|
+
// Positive evidence: text contains active working indicators
|
|
161
|
+
if (WORKING_RE.test(text)) {
|
|
162
|
+
return {
|
|
163
|
+
verdict: "WORKING",
|
|
164
|
+
wake: 0,
|
|
165
|
+
reason_codes: ["ALL_CLEAR"],
|
|
166
|
+
prompt_budget_class: "silent",
|
|
167
|
+
dirty_out_of_scope,
|
|
168
|
+
last_marker: undefined,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
// Conservative bias: ambiguous (empty text with no stale trigger, or non-empty
|
|
172
|
+
// without positive WORKING evidence) → UNKNOWN_NEEDS_READ, wake=1
|
|
173
|
+
return {
|
|
174
|
+
verdict: "UNKNOWN_NEEDS_READ",
|
|
175
|
+
wake: 1,
|
|
176
|
+
reason_codes: ["AMBIGUOUS_STATE"],
|
|
177
|
+
prompt_budget_class: "compact",
|
|
178
|
+
dirty_out_of_scope,
|
|
179
|
+
last_marker: undefined,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=classifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classifier.js","sourceRoot":"","sources":["../../../src/odin-watch/classifier.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
3
|
+
import { CmuxBackend } from "./backends/cmux.js";
|
|
4
|
+
import { TmuxBackend } from "./backends/tmux.js";
|
|
5
|
+
import { classify } from "./classifier.js";
|
|
6
|
+
import { writeWakeFiles } from "./writers.js";
|
|
7
|
+
import { execFileNoThrow } from "../utils/execFileNoThrow.js";
|
|
8
|
+
const VALID_SUBSTRATES = [
|
|
9
|
+
"cmux",
|
|
10
|
+
"tmux",
|
|
11
|
+
"minimux",
|
|
12
|
+
"herdr",
|
|
13
|
+
"plain",
|
|
14
|
+
];
|
|
15
|
+
function isValidSubstrate(s) {
|
|
16
|
+
return VALID_SUBSTRATES.includes(s);
|
|
17
|
+
}
|
|
18
|
+
function parseCliArgs() {
|
|
19
|
+
const { values } = parseArgs({
|
|
20
|
+
options: {
|
|
21
|
+
surface: { type: "string" },
|
|
22
|
+
substrate: { type: "string" },
|
|
23
|
+
"write-scope": { type: "string", multiple: true },
|
|
24
|
+
interval: { type: "string", default: "120" },
|
|
25
|
+
"mandatory-audit": { type: "string", default: "600" },
|
|
26
|
+
"flag-dir": {
|
|
27
|
+
type: "string",
|
|
28
|
+
default: ".odin/monitor/wake/",
|
|
29
|
+
},
|
|
30
|
+
"archive-dir": {
|
|
31
|
+
type: "string",
|
|
32
|
+
default: ".odin/monitor/cmux/",
|
|
33
|
+
},
|
|
34
|
+
label: { type: "string" },
|
|
35
|
+
},
|
|
36
|
+
strict: true,
|
|
37
|
+
});
|
|
38
|
+
const surface = values["surface"];
|
|
39
|
+
const substrate = values["substrate"];
|
|
40
|
+
const label = values["label"];
|
|
41
|
+
if (!surface) {
|
|
42
|
+
console.error("Error: --surface <id> is required");
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
if (!substrate) {
|
|
46
|
+
console.error("Error: --substrate <type> is required");
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
if (!isValidSubstrate(substrate)) {
|
|
50
|
+
console.error(`Error: --substrate must be one of: ${VALID_SUBSTRATES.join(", ")}`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
if (!label) {
|
|
54
|
+
console.error("Error: --label <name> is required");
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
const intervalSeconds = parseInt(values["interval"] ?? "120", 10);
|
|
58
|
+
const mandatoryAuditSeconds = parseInt(values["mandatory-audit"] ?? "600", 10);
|
|
59
|
+
if (isNaN(intervalSeconds) || intervalSeconds <= 0) {
|
|
60
|
+
console.error("Error: --interval must be a positive integer");
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
if (isNaN(mandatoryAuditSeconds) || mandatoryAuditSeconds <= 0) {
|
|
64
|
+
console.error("Error: --mandatory-audit must be a positive integer");
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
surface,
|
|
69
|
+
substrate,
|
|
70
|
+
writeScope: values["write-scope"] ?? [],
|
|
71
|
+
intervalSeconds,
|
|
72
|
+
mandatoryAuditSeconds,
|
|
73
|
+
flagDir: values["flag-dir"] ?? ".odin/monitor/wake/",
|
|
74
|
+
archiveDir: values["archive-dir"] ?? ".odin/monitor/cmux/",
|
|
75
|
+
label,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
async function getDirtyPaths() {
|
|
79
|
+
const result = await execFileNoThrow("git", [
|
|
80
|
+
"diff",
|
|
81
|
+
"--name-only",
|
|
82
|
+
"--diff-filter=ACDMRT",
|
|
83
|
+
]);
|
|
84
|
+
if (result.status !== 0)
|
|
85
|
+
return [];
|
|
86
|
+
return result.stdout
|
|
87
|
+
.split("\n")
|
|
88
|
+
.map((l) => l.trim())
|
|
89
|
+
.filter(Boolean);
|
|
90
|
+
}
|
|
91
|
+
async function getCurrentBranch() {
|
|
92
|
+
const result = await execFileNoThrow("git", [
|
|
93
|
+
"rev-parse",
|
|
94
|
+
"--abbrev-ref",
|
|
95
|
+
"HEAD",
|
|
96
|
+
]);
|
|
97
|
+
return result.status === 0 ? result.stdout.trim() : undefined;
|
|
98
|
+
}
|
|
99
|
+
async function getCurrentHead() {
|
|
100
|
+
const result = await execFileNoThrow("git", ["rev-parse", "--short", "HEAD"]);
|
|
101
|
+
return result.status === 0 ? result.stdout.trim() : undefined;
|
|
102
|
+
}
|
|
103
|
+
async function sleep(ms) {
|
|
104
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
105
|
+
}
|
|
106
|
+
async function main() {
|
|
107
|
+
const { surface, substrate, writeScope, intervalSeconds, mandatoryAuditSeconds, flagDir, archiveDir, label, } = parseCliArgs();
|
|
108
|
+
const intervalMs = intervalSeconds * 1000;
|
|
109
|
+
const mandatoryAuditIntervalMs = mandatoryAuditSeconds * 1000;
|
|
110
|
+
// Select backend based on substrate
|
|
111
|
+
let snapshotter;
|
|
112
|
+
switch (substrate) {
|
|
113
|
+
case "cmux":
|
|
114
|
+
snapshotter = new CmuxBackend(archiveDir);
|
|
115
|
+
break;
|
|
116
|
+
case "tmux":
|
|
117
|
+
snapshotter = new TmuxBackend(archiveDir);
|
|
118
|
+
break;
|
|
119
|
+
default:
|
|
120
|
+
throw new Error(`Substrate '${substrate}' is not yet supported by odin-watch. Supported: cmux, tmux`);
|
|
121
|
+
}
|
|
122
|
+
let prevHash = null;
|
|
123
|
+
let lastVerdictState = null;
|
|
124
|
+
let lastMandatoryAuditTs = null;
|
|
125
|
+
const startTs = Date.now();
|
|
126
|
+
console.error(`odin-watch starting: surface=${surface} substrate=${substrate} label=${label} interval=${intervalSeconds}s`);
|
|
127
|
+
while (true) {
|
|
128
|
+
const loopStart = Date.now();
|
|
129
|
+
const elapsed = loopStart - startTs;
|
|
130
|
+
try {
|
|
131
|
+
// Capture
|
|
132
|
+
const snapshot = await snapshotter.capture(surface);
|
|
133
|
+
// Gather dirty paths and git metadata
|
|
134
|
+
const [dirtyPaths, branch, head] = await Promise.all([
|
|
135
|
+
getDirtyPaths(),
|
|
136
|
+
getCurrentBranch(),
|
|
137
|
+
getCurrentHead(),
|
|
138
|
+
]);
|
|
139
|
+
// Classify
|
|
140
|
+
const result = classify({
|
|
141
|
+
text: snapshot.text,
|
|
142
|
+
hash: snapshot.hash,
|
|
143
|
+
prev_hash: prevHash,
|
|
144
|
+
elapsed_ms: elapsed,
|
|
145
|
+
now_ms: Date.now(),
|
|
146
|
+
mandatory_audit_interval_ms: mandatoryAuditIntervalMs,
|
|
147
|
+
stale_threshold_ms: intervalMs * 3, // stale after 3 missed polls
|
|
148
|
+
write_scope: writeScope,
|
|
149
|
+
dirty_paths: dirtyPaths,
|
|
150
|
+
last_verdict: lastVerdictState,
|
|
151
|
+
last_mandatory_audit_ts: lastMandatoryAuditTs,
|
|
152
|
+
});
|
|
153
|
+
// If mandatory audit triggered, reset the audit clock
|
|
154
|
+
if (result.reason_codes.includes("MANDATORY_AUDIT interval elapsed")) {
|
|
155
|
+
lastMandatoryAuditTs = loopStart;
|
|
156
|
+
}
|
|
157
|
+
// Build WakeState
|
|
158
|
+
const wakeState = {
|
|
159
|
+
schema_version: "1.0",
|
|
160
|
+
ts: new Date(loopStart).toISOString(),
|
|
161
|
+
surface,
|
|
162
|
+
pane_id: surface,
|
|
163
|
+
substrate: substrate,
|
|
164
|
+
state: result.verdict,
|
|
165
|
+
wake: result.wake,
|
|
166
|
+
reason_codes: result.reason_codes,
|
|
167
|
+
dirty_paths: dirtyPaths,
|
|
168
|
+
dirty_out_of_scope: result.dirty_out_of_scope,
|
|
169
|
+
screen_hash: snapshot.hash,
|
|
170
|
+
screen_changed: prevHash !== null && prevHash !== snapshot.hash,
|
|
171
|
+
last_marker: result.last_marker,
|
|
172
|
+
prompt_budget_class: result.prompt_budget_class,
|
|
173
|
+
next_mandatory_audit_due: new Date(loopStart + mandatoryAuditIntervalMs).toISOString(),
|
|
174
|
+
head,
|
|
175
|
+
branch,
|
|
176
|
+
};
|
|
177
|
+
// Write output files
|
|
178
|
+
writeWakeFiles(flagDir, label, wakeState);
|
|
179
|
+
prevHash = snapshot.hash;
|
|
180
|
+
lastVerdictState = result.verdict;
|
|
181
|
+
console.error(`[${wakeState.ts}] verdict=${result.verdict} wake=${result.wake} hash=${snapshot.hash.slice(0, 8)}`);
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
185
|
+
console.error(`odin-watch poll error: ${msg}`);
|
|
186
|
+
}
|
|
187
|
+
// Sleep until next interval
|
|
188
|
+
const elapsed2 = Date.now() - loopStart;
|
|
189
|
+
const remaining = intervalMs - elapsed2;
|
|
190
|
+
if (remaining > 0) {
|
|
191
|
+
await sleep(remaining);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
main().catch((err) => {
|
|
196
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
197
|
+
console.error(`odin-watch fatal: ${msg}`);
|
|
198
|
+
process.exitCode = 1;
|
|
199
|
+
});
|
|
200
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/odin-watch/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { SubstrateType } from "../protocol/schemas.js";
|
|
2
|
+
export interface Snapshot {
|
|
3
|
+
pane_id: string;
|
|
4
|
+
substrate: SubstrateType;
|
|
5
|
+
text: string;
|
|
6
|
+
hash: string;
|
|
7
|
+
ts: number;
|
|
8
|
+
}
|
|
9
|
+
export interface Snapshotter {
|
|
10
|
+
capture(pane_id: string): Promise<Snapshot>;
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshotter.js","sourceRoot":"","sources":["../../../src/odin-watch/snapshotter.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { WakeState } from "../protocol/schemas.js";
|
|
2
|
+
/**
|
|
3
|
+
* Write the three output files for a single poll result:
|
|
4
|
+
* {flagDir}/{label}.flag — exactly one byte: "0" or "1", no newline
|
|
5
|
+
* {flagDir}/{label}.reason — text reason codes, one per line
|
|
6
|
+
* {flagDir}/{label}.state.json — WakeState JSON object
|
|
7
|
+
*/
|
|
8
|
+
export declare function writeWakeFiles(flagDir: string, label: string, state: WakeState): void;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Write the three output files for a single poll result:
|
|
5
|
+
* {flagDir}/{label}.flag — exactly one byte: "0" or "1", no newline
|
|
6
|
+
* {flagDir}/{label}.reason — text reason codes, one per line
|
|
7
|
+
* {flagDir}/{label}.state.json — WakeState JSON object
|
|
8
|
+
*/
|
|
9
|
+
export function writeWakeFiles(flagDir, label, state) {
|
|
10
|
+
mkdirSync(flagDir, { recursive: true });
|
|
11
|
+
// Flag file: exactly one byte ASCII character "0" or "1", no newline
|
|
12
|
+
const flagPath = join(flagDir, `${label}.flag`);
|
|
13
|
+
writeFileSync(flagPath, String(state.wake), {
|
|
14
|
+
encoding: "utf8",
|
|
15
|
+
});
|
|
16
|
+
// Reason file: one reason code per line
|
|
17
|
+
const reasonPath = join(flagDir, `${label}.reason`);
|
|
18
|
+
writeFileSync(reasonPath, state.reason_codes.join("\n"), {
|
|
19
|
+
encoding: "utf8",
|
|
20
|
+
});
|
|
21
|
+
// State JSON file
|
|
22
|
+
const statePath = join(flagDir, `${label}.state.json`);
|
|
23
|
+
writeFileSync(statePath, JSON.stringify(state, null, 2), {
|
|
24
|
+
encoding: "utf8",
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=writers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"writers.js","sourceRoot":"","sources":["../../../src/odin-watch/writers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAC5B,OAAe,EACf,KAAa,EACb,KAAgB;IAEhB,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAExC,qEAAqE;IACrE,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC;IAChD,aAAa,CAAC,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,CAAc,EAAE;QACvD,QAAQ,EAAE,MAAM;KACjB,CAAC,CAAC;IAEH,wCAAwC;IACxC,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,KAAK,SAAS,CAAC,CAAC;IACpD,aAAa,CAAC,UAAU,EAAE,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QACvD,QAAQ,EAAE,MAAM;KACjB,CAAC,CAAC;IAEH,kBAAkB;IAClB,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,KAAK,aAAa,CAAC,CAAC;IACvD,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;QACvD,QAAQ,EAAE,MAAM;KACjB,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { GOVERNED_CONTEXT_PROOF_SCHEMA, VERSION, classifyGovernedReadiness, createProtocolService, exportProtocolSnapshot, getActivationGates, getCloseoutChecklist, getDelegationPacket, getOnboardingPlan, getRoleProfile, getRuntimeNotice, getStartupPacket, harnessCategory, validateBootReceipt, validateCmuxDeliveryProof, validateDelegationPacket, validateGovernedContextProof, validateInstructionReadProof, validateTeamManifest } from "./service.js";
|
|
2
2
|
export type { AssuranceLevel, CloseoutMode, DelegationPacketInput, GovernedReadinessInput, GovernedReadinessResult, GovernedReadinessState, HarnessCategory, ProtocolData, RuntimeNotice, StartupPacket, StartupPacketInput, ValidationResult } from "./service.js";
|
|
3
|
-
export { computeSurfaceLayout, computeSurfaceLayoutGate, renderSurfaceLayoutAscii } from "./surface-layout.js";
|
|
3
|
+
export { computeSurfaceLayout, computeSurfaceLayoutGate, getSubstrateCapability, renderSurfaceLayoutAscii } from "./surface-layout.js";
|
|
4
4
|
export type { SurfaceLayout, SurfaceLayoutColumn, SurfaceLayoutSurface, SurfaceLayoutGate } from "./surface-layout.js";
|
|
5
|
+
export { CapabilityFlag } from "./schemas.js";
|
|
6
|
+
export type { BootReceipt, DeliveryReceiptPacingExtension, HarnessPacingEvent, PacingEventType, PromptBudgetClass, ReceiptType, SubstrateCapability, SubstrateType, WakeState, WakeVerdict } from "./schemas.js";
|
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
export { GOVERNED_CONTEXT_PROOF_SCHEMA, VERSION, classifyGovernedReadiness, createProtocolService, exportProtocolSnapshot, getActivationGates, getCloseoutChecklist, getDelegationPacket, getOnboardingPlan, getRoleProfile, getRuntimeNotice, getStartupPacket, harnessCategory, validateBootReceipt, validateCmuxDeliveryProof, validateDelegationPacket, validateGovernedContextProof, validateInstructionReadProof, validateTeamManifest } from "./service.js";
|
|
2
|
-
export { computeSurfaceLayout, computeSurfaceLayoutGate, renderSurfaceLayoutAscii } from "./surface-layout.js";
|
|
2
|
+
export { computeSurfaceLayout, computeSurfaceLayoutGate, getSubstrateCapability, renderSurfaceLayoutAscii } from "./surface-layout.js";
|
|
3
|
+
// New types for SCP performance/portability program (E1-E5 unblocking)
|
|
4
|
+
// CapabilityFlag is exported as a value (runtime const) and as a type via the same export.
|
|
5
|
+
export { CapabilityFlag } from "./schemas.js";
|
|
3
6
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/protocol/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,6BAA6B,EAC7B,OAAO,EACP,yBAAyB,EACzB,qBAAqB,EACrB,sBAAsB,EACtB,kBAAkB,EAClB,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,mBAAmB,EACnB,yBAAyB,EACzB,wBAAwB,EACxB,4BAA4B,EAC5B,4BAA4B,EAC5B,oBAAoB,EACrB,MAAM,cAAc,CAAC;AAiBtB,OAAO,EACL,oBAAoB,EACpB,wBAAwB,EACxB,wBAAwB,EACzB,MAAM,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/protocol/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,6BAA6B,EAC7B,OAAO,EACP,yBAAyB,EACzB,qBAAqB,EACrB,sBAAsB,EACtB,kBAAkB,EAClB,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,mBAAmB,EACnB,yBAAyB,EACzB,wBAAwB,EACxB,4BAA4B,EAC5B,4BAA4B,EAC5B,oBAAoB,EACrB,MAAM,cAAc,CAAC;AAiBtB,OAAO,EACL,oBAAoB,EACpB,wBAAwB,EACxB,sBAAsB,EACtB,wBAAwB,EACzB,MAAM,qBAAqB,CAAC;AAS7B,uEAAuE;AACvE,2FAA2F;AAC3F,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -15,6 +15,20 @@ export type ProtocolData = {
|
|
|
15
15
|
harnessSkillTargets: string;
|
|
16
16
|
teamBootstrapRunbook: string;
|
|
17
17
|
};
|
|
18
|
+
roleCards: {
|
|
19
|
+
execPm: string;
|
|
20
|
+
teamPm: string;
|
|
21
|
+
devWorker: string;
|
|
22
|
+
qaWorker: string;
|
|
23
|
+
execAsst: string;
|
|
24
|
+
};
|
|
25
|
+
missionFrontrun: {
|
|
26
|
+
orchestratorContract: string;
|
|
27
|
+
workerContract: string;
|
|
28
|
+
scrutinyValidatorContract: string;
|
|
29
|
+
scrutinyFeatureReviewerContract: string;
|
|
30
|
+
droidsScrutinyFeatureReviewer: string;
|
|
31
|
+
};
|
|
18
32
|
};
|
|
19
33
|
export type ProtocolRepository = {
|
|
20
34
|
load(): ProtocolData;
|
|
@@ -17,7 +17,17 @@ export const REQUIRED_PROTOCOL_FILES = [
|
|
|
17
17
|
"protocol/skill-references/boot-receipt-examples.md",
|
|
18
18
|
"protocol/skill-references/canonical-introduction-prompt.md",
|
|
19
19
|
"protocol/skill-references/harness-skill-targets.md",
|
|
20
|
-
"protocol/skill-references/team-bootstrap-runbook.md"
|
|
20
|
+
"protocol/skill-references/team-bootstrap-runbook.md",
|
|
21
|
+
"protocol/role-cards/exec-pm.md",
|
|
22
|
+
"protocol/role-cards/team-pm.md",
|
|
23
|
+
"protocol/role-cards/dev-worker.md",
|
|
24
|
+
"protocol/role-cards/qa-worker.md",
|
|
25
|
+
"protocol/role-cards/exec-asst.md",
|
|
26
|
+
"protocol/mission-frontrun/orchestrator-contract.md",
|
|
27
|
+
"protocol/mission-frontrun/worker-contract.md",
|
|
28
|
+
"protocol/mission-frontrun/scrutiny-validator-contract.md",
|
|
29
|
+
"protocol/mission-frontrun/scrutiny-feature-reviewer-contract.md",
|
|
30
|
+
"protocol/mission-frontrun/droids-scrutiny-feature-reviewer.md"
|
|
21
31
|
];
|
|
22
32
|
function missingProtocolFiles(root) {
|
|
23
33
|
return REQUIRED_PROTOCOL_FILES.filter((file) => !existsSync(join(root, file)));
|
|
@@ -105,6 +115,20 @@ export function createFileProtocolRepository(rootDir = findRootDir()) {
|
|
|
105
115
|
canonicalIntroductionPrompt: readFileSync(protocolPath("skill-references", "canonical-introduction-prompt.md"), "utf8"),
|
|
106
116
|
harnessSkillTargets: readFileSync(protocolPath("skill-references", "harness-skill-targets.md"), "utf8"),
|
|
107
117
|
teamBootstrapRunbook: readFileSync(protocolPath("skill-references", "team-bootstrap-runbook.md"), "utf8")
|
|
118
|
+
},
|
|
119
|
+
roleCards: {
|
|
120
|
+
execPm: readFileSync(protocolPath("role-cards", "exec-pm.md"), "utf8"),
|
|
121
|
+
teamPm: readFileSync(protocolPath("role-cards", "team-pm.md"), "utf8"),
|
|
122
|
+
devWorker: readFileSync(protocolPath("role-cards", "dev-worker.md"), "utf8"),
|
|
123
|
+
qaWorker: readFileSync(protocolPath("role-cards", "qa-worker.md"), "utf8"),
|
|
124
|
+
execAsst: readFileSync(protocolPath("role-cards", "exec-asst.md"), "utf8")
|
|
125
|
+
},
|
|
126
|
+
missionFrontrun: {
|
|
127
|
+
orchestratorContract: readFileSync(protocolPath("mission-frontrun", "orchestrator-contract.md"), "utf8"),
|
|
128
|
+
workerContract: readFileSync(protocolPath("mission-frontrun", "worker-contract.md"), "utf8"),
|
|
129
|
+
scrutinyValidatorContract: readFileSync(protocolPath("mission-frontrun", "scrutiny-validator-contract.md"), "utf8"),
|
|
130
|
+
scrutinyFeatureReviewerContract: readFileSync(protocolPath("mission-frontrun", "scrutiny-feature-reviewer-contract.md"), "utf8"),
|
|
131
|
+
droidsScrutinyFeatureReviewer: readFileSync(protocolPath("mission-frontrun", "droids-scrutiny-feature-reviewer.md"), "utf8")
|
|
108
132
|
}
|
|
109
133
|
});
|
|
110
134
|
cache = { fingerprint, data };
|