@bradheitmann/odin-sentinel 0.4.4 → 0.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +64 -0
- package/CLAUDE.md +43 -0
- package/README.md +102 -335
- package/dist/src/mcp/server.js +43 -12
- package/dist/src/mcp/server.js.map +1 -1
- package/dist/src/protocol/schemas.d.ts +2529 -4
- package/dist/src/protocol/schemas.js +214 -18
- package/dist/src/protocol/schemas.js.map +1 -1
- package/dist/src/protocol/service.d.ts +96 -2
- package/dist/src/protocol/service.js +516 -4
- package/dist/src/protocol/service.js.map +1 -1
- package/dist/src/protocol/surface-layout.d.ts +40 -1
- package/dist/src/protocol/surface-layout.js +98 -1
- package/dist/src/protocol/surface-layout.js.map +1 -1
- package/dist/src/protocol/validators.d.ts +3 -0
- package/dist/src/protocol/validators.js +28 -0
- package/dist/src/protocol/validators.js.map +1 -1
- package/dist/src/protocol/version.d.ts +3 -0
- package/dist/src/protocol/version.js +3 -0
- package/dist/src/protocol/version.js.map +1 -1
- package/dist/src/telemetry/config.d.ts +8 -0
- package/dist/src/telemetry/config.js +24 -0
- package/dist/src/telemetry/config.js.map +1 -1
- package/dist/src/telemetry/index.d.ts +5 -5
- package/dist/src/telemetry/index.js +3 -3
- package/dist/src/telemetry/index.js.map +1 -1
- package/dist/src/telemetry/redactor.js +25 -7
- package/dist/src/telemetry/redactor.js.map +1 -1
- package/dist/src/telemetry/report.d.ts +108 -0
- package/dist/src/telemetry/report.js +83 -3
- package/dist/src/telemetry/report.js.map +1 -1
- package/dist/src/telemetry/submit.d.ts +2 -0
- package/dist/src/telemetry/submit.js +79 -6
- package/dist/src/telemetry/submit.js.map +1 -1
- package/docs/guides/quick-start.md +112 -44
- package/docs/guides/quickstart-prompts.md +65 -0
- package/docs/guides/recommended-starter-team.md +45 -27
- package/docs/reference/client-compatibility.md +20 -43
- package/docs/reference/cost-and-privacy.md +26 -23
- package/docs/reference/distribution.md +40 -55
- package/docs/reference/public-surface-audit.md +35 -114
- package/package.json +19 -4
- package/protocol/SCP.md +8 -1
- package/protocol/bootstrap-skill.md +16 -11
- package/protocol/closeout.yaml +7 -1
- package/protocol/delegation.yaml +1 -1
- package/protocol/model-profiles.yaml +55 -1
- package/protocol/receipts/boot-receipt.yaml +42 -0
- package/protocol/receipts/team-manifest.yaml +41 -0
- package/protocol/roles.yaml +69 -1
- package/protocol/topology.yaml +78 -36
- package/scripts/audit/public-surface.mjs +48 -19
- package/scripts/audit/verify-pack.mjs +294 -27
- package/templates/dev-slice-template.md +56 -0
- package/templates/pm-role-template.md +61 -0
- package/templates/qa-slice-template.md +46 -0
- package/templates/team-manifest-template.yaml +163 -0
|
@@ -1,21 +1,8 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
2
4
|
|
|
3
|
-
const
|
|
4
|
-
encoding: "utf8",
|
|
5
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
6
|
-
});
|
|
7
|
-
|
|
8
|
-
// pnpm 11 writes JSON directly to stdout (no preamble); pnpm <=10 prefixed
|
|
9
|
-
// it with lifecycle logs. Handle both shapes: take from "\n{" if present, or
|
|
10
|
-
// from the start if output already begins with "{".
|
|
11
|
-
const newlineBrace = output.lastIndexOf("\n{");
|
|
12
|
-
const jsonStart = newlineBrace !== -1 ? newlineBrace + 1 : (output.trimStart().startsWith("{") ? output.indexOf("{") : -1);
|
|
13
|
-
if (jsonStart === -1) {
|
|
14
|
-
throw new Error("pnpm pack did not return JSON metadata");
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const pack = JSON.parse(output.slice(jsonStart));
|
|
18
|
-
const paths = new Set(pack.files.map((file) => file.path));
|
|
5
|
+
export const MINIMUM_COMPATIBLE_CHILD_MCP_VERSION = "0.4.5";
|
|
19
6
|
|
|
20
7
|
const requiredProtocolFiles = [
|
|
21
8
|
"protocol/SCP.md",
|
|
@@ -29,7 +16,14 @@ const requiredProtocolFiles = [
|
|
|
29
16
|
"protocol/bootstrap-skill.md"
|
|
30
17
|
];
|
|
31
18
|
|
|
32
|
-
const
|
|
19
|
+
const requiredTemplateFiles = [
|
|
20
|
+
"templates/dev-slice-template.md",
|
|
21
|
+
"templates/qa-slice-template.md",
|
|
22
|
+
"templates/pm-role-template.md",
|
|
23
|
+
"templates/team-manifest-template.yaml"
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
export const requiredPackageFiles = [
|
|
33
27
|
"dist/src/bin/index.js",
|
|
34
28
|
"dist/src/mcp/server.js",
|
|
35
29
|
"dist/src/protocol/index.js",
|
|
@@ -39,34 +33,307 @@ const requiredFiles = [
|
|
|
39
33
|
"dist/src/protocol/validators.js",
|
|
40
34
|
"dist/src/protocol/version.js",
|
|
41
35
|
"docs/guides/quick-start.md",
|
|
36
|
+
"docs/guides/quickstart-prompts.md",
|
|
42
37
|
"docs/guides/recommended-starter-team.md",
|
|
43
38
|
"docs/reference/client-compatibility.md",
|
|
44
39
|
"docs/reference/cost-and-privacy.md",
|
|
45
40
|
"docs/reference/distribution.md",
|
|
46
41
|
"docs/reference/public-surface-audit.md",
|
|
47
42
|
...requiredProtocolFiles,
|
|
43
|
+
...requiredTemplateFiles,
|
|
48
44
|
"scripts/audit/public-surface.mjs",
|
|
49
45
|
"scripts/audit/verify-pack.mjs",
|
|
46
|
+
"AGENTS.md",
|
|
47
|
+
"CLAUDE.md",
|
|
50
48
|
"README.md",
|
|
51
49
|
"LICENSE",
|
|
52
50
|
"package.json"
|
|
53
51
|
];
|
|
54
52
|
|
|
55
|
-
const
|
|
56
|
-
if (missing.length > 0) {
|
|
57
|
-
throw new Error(`Package is missing required files: ${missing.join(", ")}`);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const staleFiles = [
|
|
53
|
+
const staleBuildFiles = [
|
|
61
54
|
"dist/src/index.js",
|
|
62
55
|
"dist/src/server.js",
|
|
63
56
|
"dist/src/protocol.js",
|
|
64
57
|
"dist/src/protocol-repository.js",
|
|
65
58
|
"dist/src/validators.js"
|
|
66
|
-
]
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
const protocolResourceVersionLockedFiles = new Set([
|
|
62
|
+
"protocol/SCP.md",
|
|
63
|
+
"protocol/closeout.yaml",
|
|
64
|
+
"protocol/delegation.yaml",
|
|
65
|
+
"protocol/model-profiles.yaml",
|
|
66
|
+
"protocol/roles.yaml",
|
|
67
|
+
"protocol/topology.yaml"
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
const forbiddenPackagePrefixes = ["project/" + "planning" + "/", "." + "edge-" + "agentic" + "/local/"];
|
|
71
|
+
const forbiddenPackagedContentRules = [
|
|
72
|
+
{ name: "local evidence path", pattern: new RegExp(`\\.${"edge-" + "agentic"}/local`, "i") },
|
|
73
|
+
{ name: "local ODIN audit path", pattern: /\.odin\/local\//i },
|
|
74
|
+
{ name: "private planning path", pattern: new RegExp(`project/${"planning"}/`, "i") },
|
|
75
|
+
{ name: "macOS home path", pattern: new RegExp(`/${"Users"}/[A-Za-z0-9._-]+/`) },
|
|
76
|
+
{ name: "Linux home path", pattern: /\/home\/[A-Za-z0-9._-]+\// },
|
|
77
|
+
{ name: "secret-looking assignment", pattern: /(api[_-]?key|secret|token|password)\s*[:=]\s*["'][^"']+["']/i }
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
function asPathSet(paths) {
|
|
81
|
+
return new Set(Array.from(paths));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function validatePackageMetadata(packageJson) {
|
|
85
|
+
const errors = [];
|
|
86
|
+
if (!packageJson.repository?.url) errors.push("package.json missing repository.url");
|
|
87
|
+
if (!packageJson.homepage) errors.push("package.json missing homepage");
|
|
88
|
+
if (!packageJson.bugs?.url) errors.push("package.json missing bugs.url");
|
|
89
|
+
if (!packageJson.license) errors.push("package.json missing license");
|
|
90
|
+
if (!packageJson.engines?.node) errors.push("package.json missing engines.node");
|
|
91
|
+
if (!Array.isArray(packageJson.files) || packageJson.files.length === 0) errors.push("package.json missing files allowlist");
|
|
92
|
+
for (const file of ["docs", "protocol", "templates", "AGENTS.md", "CLAUDE.md", "README.md", "LICENSE"]) {
|
|
93
|
+
if (!packageJson.files?.includes(file)) errors.push(`package.json files allowlist missing ${file}`);
|
|
94
|
+
}
|
|
95
|
+
if (packageJson.odin?.publicVersion !== packageJson.version) {
|
|
96
|
+
errors.push("package.json odin.publicVersion must match package version");
|
|
97
|
+
}
|
|
98
|
+
if (packageJson.odin?.minimumCompatibleChildMcpVersion !== MINIMUM_COMPATIBLE_CHILD_MCP_VERSION) {
|
|
99
|
+
errors.push("package.json odin.minimumCompatibleChildMcpVersion drifted");
|
|
100
|
+
}
|
|
101
|
+
for (const [name, version] of Object.entries(packageJson.dependencies ?? {})) {
|
|
102
|
+
if (typeof version !== "string" || /^[~^]/.test(version) || /[<>=*x|]/i.test(version)) {
|
|
103
|
+
errors.push(`package.json runtime dependency ${name} must be pinned exactly`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return errors;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function validatePackFileList(pathsInput) {
|
|
110
|
+
const paths = asPathSet(pathsInput);
|
|
111
|
+
const errors = [];
|
|
112
|
+
const missing = requiredPackageFiles.filter((file) => !paths.has(file));
|
|
113
|
+
if (missing.length > 0) errors.push(`Package is missing required files: ${missing.join(", ")}`);
|
|
114
|
+
|
|
115
|
+
const stale = staleBuildFiles.filter((file) => paths.has(file));
|
|
116
|
+
if (stale.length > 0) errors.push(`Package includes stale build files: ${stale.join(", ")}`);
|
|
117
|
+
|
|
118
|
+
const privatePaths = Array.from(paths).filter((file) => forbiddenPackagePrefixes.some((prefix) => file.startsWith(prefix)));
|
|
119
|
+
if (privatePaths.length > 0) errors.push(`Package includes private local paths: ${privatePaths.join(", ")}`);
|
|
67
120
|
|
|
68
|
-
|
|
69
|
-
throw new Error(`Package includes stale build files: ${staleFiles.join(", ")}`);
|
|
121
|
+
return errors;
|
|
70
122
|
}
|
|
71
123
|
|
|
72
|
-
|
|
124
|
+
export function validatePackFileContents(fileTextByPath) {
|
|
125
|
+
const findings = [];
|
|
126
|
+
for (const [file, text] of Object.entries(fileTextByPath)) {
|
|
127
|
+
for (const rule of forbiddenPackagedContentRules) {
|
|
128
|
+
if (rule.pattern.test(text)) findings.push(`${file}: ${rule.name}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return findings;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function isTextPackageFile(file) {
|
|
135
|
+
return /\.(js|mjs|cjs|ts|json|md|ya?ml|txt|html|css|sh)$/.test(file) || !file.includes(".");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function readPackFileTexts(paths) {
|
|
139
|
+
return Object.fromEntries(
|
|
140
|
+
paths
|
|
141
|
+
.filter(isTextPackageFile)
|
|
142
|
+
.flatMap((file) => {
|
|
143
|
+
try {
|
|
144
|
+
return [[file, readFileSync(file, "utf8")]];
|
|
145
|
+
} catch {
|
|
146
|
+
return [];
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function findStaleVersionReferences(fileTextByPath, currentVersion, minimumCompatibleVersion = MINIMUM_COMPATIBLE_CHILD_MCP_VERSION) {
|
|
153
|
+
const allowed = new Set([currentVersion, minimumCompatibleVersion]);
|
|
154
|
+
const findings = [];
|
|
155
|
+
const versionPattern = /\b\d+\.\d+\.\d+\b/g;
|
|
156
|
+
const relevantVersionLine = (line) => {
|
|
157
|
+
const lower = line.toLowerCase();
|
|
158
|
+
if (/\bnode\.?js\b/.test(lower) || /\bengines?\b/.test(lower)) return false;
|
|
159
|
+
if (/^version:\s*\d+\.\d+\.\d+\s*$/.test(line.trim())) return false;
|
|
160
|
+
return [
|
|
161
|
+
"odin-sentinel",
|
|
162
|
+
"serverinfo",
|
|
163
|
+
"package/server",
|
|
164
|
+
"public version",
|
|
165
|
+
"minimum compatible",
|
|
166
|
+
"compatible child mcp",
|
|
167
|
+
"mcp version",
|
|
168
|
+
"server version",
|
|
169
|
+
"expected version",
|
|
170
|
+
"stale mcp version",
|
|
171
|
+
"confirm >=",
|
|
172
|
+
"server "
|
|
173
|
+
].some((marker) => lower.includes(marker));
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
for (const [file, text] of Object.entries(fileTextByPath)) {
|
|
177
|
+
for (const line of text.split("\n")) {
|
|
178
|
+
if (!relevantVersionLine(line)) continue;
|
|
179
|
+
const matches = line.match(versionPattern) ?? [];
|
|
180
|
+
for (const version of matches) {
|
|
181
|
+
if (!allowed.has(version)) findings.push(`${file}: stale version reference ${version}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return findings;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function validatePublicProtocolSync({ scpText, bootstrapText, currentVersion, minimumCompatibleVersion = MINIMUM_COMPATIBLE_CHILD_MCP_VERSION }) {
|
|
190
|
+
const errors = [];
|
|
191
|
+
const requiredMarkers = [
|
|
192
|
+
`SCP_PUBLIC_VERSION: ${currentVersion}`,
|
|
193
|
+
`MIN_COMPATIBLE_CHILD_MCP: ${minimumCompatibleVersion}`
|
|
194
|
+
];
|
|
195
|
+
|
|
196
|
+
for (const marker of requiredMarkers) {
|
|
197
|
+
if (!scpText.includes(marker)) errors.push(`protocol/SCP.md missing ${marker}`);
|
|
198
|
+
if (!bootstrapText.includes(marker)) errors.push(`protocol/bootstrap-skill.md missing ${marker}`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return errors;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function validatePluginSync({ pluginManifestText, pluginSkillText, pluginReadmeText, currentVersion, minimumCompatibleVersion = MINIMUM_COMPATIBLE_CHILD_MCP_VERSION }) {
|
|
205
|
+
const errors = [];
|
|
206
|
+
let manifest;
|
|
207
|
+
try {
|
|
208
|
+
manifest = JSON.parse(pluginManifestText);
|
|
209
|
+
} catch {
|
|
210
|
+
errors.push("Claude plugin manifest must be valid JSON");
|
|
211
|
+
manifest = {};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (manifest.version !== currentVersion) {
|
|
215
|
+
errors.push(`Claude plugin manifest version ${manifest.version ?? "<missing>"} must match package version ${currentVersion}`);
|
|
216
|
+
}
|
|
217
|
+
const server = manifest.mcpServers?.["odin-sentinel"];
|
|
218
|
+
if (!server) {
|
|
219
|
+
errors.push("Claude plugin manifest missing odin-sentinel MCP server");
|
|
220
|
+
} else {
|
|
221
|
+
if (server.command !== "npx") errors.push("Claude plugin odin-sentinel server must use npx");
|
|
222
|
+
const args = Array.isArray(server.args) ? server.args : [];
|
|
223
|
+
for (const requiredArg of ["-y", "-p", "@bradheitmann/odin-sentinel", "odin-sentinel-mcp"]) {
|
|
224
|
+
if (!args.includes(requiredArg)) errors.push(`Claude plugin odin-sentinel args missing ${requiredArg}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
for (const marker of [`SCP_PUBLIC_VERSION: ${currentVersion}`, `MIN_COMPATIBLE_CHILD_MCP: ${minimumCompatibleVersion}`]) {
|
|
229
|
+
if (!pluginSkillText.includes(marker)) errors.push(`Claude plugin skill missing ${marker}`);
|
|
230
|
+
}
|
|
231
|
+
if (!/23\s+`?odin\.\*`?\s+tools/i.test(pluginReadmeText)) {
|
|
232
|
+
errors.push("Claude plugin README must advertise 23 odin.* tools");
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return errors;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export function validatePackagedProtocolVersions(fileTextByPath, currentVersion) {
|
|
239
|
+
const errors = [];
|
|
240
|
+
for (const [file, text] of Object.entries(fileTextByPath)) {
|
|
241
|
+
if (!protocolResourceVersionLockedFiles.has(file)) continue;
|
|
242
|
+
const firstVersion = text.match(/^(?:version|Version):\s*([0-9]+\.[0-9]+\.[0-9]+)\s*$/m);
|
|
243
|
+
if (firstVersion && firstVersion[1] !== currentVersion) {
|
|
244
|
+
errors.push(`${file}: protocol resource version ${firstVersion[1]} must match package version ${currentVersion}`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return errors;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function validateBootstrapReadiness(bootstrapText) {
|
|
251
|
+
const required = ["MCP server", "native skill", "full prompt fallback", "CMUX", "auth/account readiness", "local inference", "role compatibility"];
|
|
252
|
+
return required.filter((term) => !bootstrapText.toLowerCase().includes(term.toLowerCase())).map((term) => `protocol/bootstrap-skill.md missing readiness term: ${term}`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export function validateTelemetryWording(costPrivacyText) {
|
|
256
|
+
const errors = [];
|
|
257
|
+
if (/does not .*telemetry/i.test(costPrivacyText) && !/optional|user-invoked|not automatic/i.test(costPrivacyText)) {
|
|
258
|
+
errors.push("Telemetry wording must explain optional/user-invoked behavior");
|
|
259
|
+
}
|
|
260
|
+
if (!/optional telemetry/i.test(costPrivacyText) || !/user-invoked/i.test(costPrivacyText)) {
|
|
261
|
+
errors.push("Cost/privacy docs must describe optional user-invoked telemetry");
|
|
262
|
+
}
|
|
263
|
+
return errors;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function readPublicVersionFiles() {
|
|
267
|
+
return Object.fromEntries([
|
|
268
|
+
"README.md",
|
|
269
|
+
"docs/guides/quick-start.md",
|
|
270
|
+
"docs/guides/quickstart-prompts.md",
|
|
271
|
+
"docs/reference/client-compatibility.md",
|
|
272
|
+
"docs/reference/distribution.md",
|
|
273
|
+
"docs/reference/public-surface-audit.md",
|
|
274
|
+
"protocol/SCP.md",
|
|
275
|
+
"protocol/bootstrap-skill.md",
|
|
276
|
+
"plugins/sentinel-coordination-protocol/.claude-plugin/plugin.json",
|
|
277
|
+
"plugins/sentinel-coordination-protocol/skills/sentinel-coordination-protocol/SKILL.md",
|
|
278
|
+
"plugins/sentinel-coordination-protocol/README.md"
|
|
279
|
+
].map((file) => [file, readFileSync(file, "utf8")]));
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function parsePackOutput(output) {
|
|
283
|
+
const newlineBrace = output.lastIndexOf("\n{");
|
|
284
|
+
const jsonStart = newlineBrace !== -1 ? newlineBrace + 1 : (output.trimStart().startsWith("{") ? output.indexOf("{") : -1);
|
|
285
|
+
if (jsonStart === -1) throw new Error("pnpm pack did not return JSON metadata");
|
|
286
|
+
return JSON.parse(output.slice(jsonStart));
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export function runVerifyPack({ pack, packageJson, publicVersionFiles, costPrivacyText, packFileTextByPath }) {
|
|
290
|
+
const packPaths = pack.files.map((file) => file.path);
|
|
291
|
+
const packFileTexts = packFileTextByPath ?? readPackFileTexts(packPaths);
|
|
292
|
+
const errors = [
|
|
293
|
+
...validatePackageMetadata(packageJson),
|
|
294
|
+
...validatePackFileList(packPaths),
|
|
295
|
+
...validatePackFileContents(packFileTexts),
|
|
296
|
+
...validatePackagedProtocolVersions(packFileTexts, packageJson.version),
|
|
297
|
+
...findStaleVersionReferences(publicVersionFiles, packageJson.version),
|
|
298
|
+
...validatePublicProtocolSync({
|
|
299
|
+
scpText: publicVersionFiles["protocol/SCP.md"],
|
|
300
|
+
bootstrapText: publicVersionFiles["protocol/bootstrap-skill.md"],
|
|
301
|
+
currentVersion: packageJson.version
|
|
302
|
+
}),
|
|
303
|
+
...validatePluginSync({
|
|
304
|
+
pluginManifestText: publicVersionFiles["plugins/sentinel-coordination-protocol/.claude-plugin/plugin.json"],
|
|
305
|
+
pluginSkillText: publicVersionFiles["plugins/sentinel-coordination-protocol/skills/sentinel-coordination-protocol/SKILL.md"],
|
|
306
|
+
pluginReadmeText: publicVersionFiles["plugins/sentinel-coordination-protocol/README.md"],
|
|
307
|
+
currentVersion: packageJson.version
|
|
308
|
+
}),
|
|
309
|
+
...validateBootstrapReadiness(publicVersionFiles["protocol/bootstrap-skill.md"]),
|
|
310
|
+
...validateTelemetryWording(costPrivacyText)
|
|
311
|
+
];
|
|
312
|
+
|
|
313
|
+
if (errors.length > 0) throw new Error(`Package release sync failed:\n${errors.join("\n")}`);
|
|
314
|
+
return {
|
|
315
|
+
fileCount: pack.files.length,
|
|
316
|
+
filename: pack.filename,
|
|
317
|
+
version: packageJson.version,
|
|
318
|
+
minimumCompatibleChildMcpVersion: MINIMUM_COMPATIBLE_CHILD_MCP_VERSION
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export function main() {
|
|
323
|
+
const output = execFileSync("pnpm", ["pack", "--dry-run", "--json"], {
|
|
324
|
+
encoding: "utf8",
|
|
325
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
326
|
+
});
|
|
327
|
+
const pack = parsePackOutput(output);
|
|
328
|
+
const packageJson = JSON.parse(readFileSync("package.json", "utf8"));
|
|
329
|
+
const publicVersionFiles = readPublicVersionFiles();
|
|
330
|
+
const costPrivacyText = readFileSync("docs/reference/cost-and-privacy.md", "utf8");
|
|
331
|
+
const result = runVerifyPack({ pack, packageJson, publicVersionFiles, costPrivacyText });
|
|
332
|
+
|
|
333
|
+
console.log(`Package smoke PASS: ${result.fileCount} files included in ${result.filename}`);
|
|
334
|
+
console.log(`Release sync PASS: public version ${result.version}; minimum compatible child MCP ${result.minimumCompatibleChildMcpVersion}`);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
338
|
+
main();
|
|
339
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# DEV Slice Template
|
|
2
|
+
|
|
3
|
+
Use this as a public starter template. Replace every placeholder before launch.
|
|
4
|
+
|
|
5
|
+
## Goal
|
|
6
|
+
|
|
7
|
+
- Task: `<what to build or change>`
|
|
8
|
+
- Owner: `<DEV role slot or agent>`
|
|
9
|
+
- PM contact: `<PM role slot or human>`
|
|
10
|
+
|
|
11
|
+
## Scope
|
|
12
|
+
|
|
13
|
+
- Write-allowed files:
|
|
14
|
+
- `<path>`
|
|
15
|
+
- Read-only context:
|
|
16
|
+
- `<path or doc>`
|
|
17
|
+
- Out of scope:
|
|
18
|
+
- `<explicit non-goals>`
|
|
19
|
+
|
|
20
|
+
## Requirements
|
|
21
|
+
|
|
22
|
+
- Functional acceptance criteria:
|
|
23
|
+
- `<criterion 1>`
|
|
24
|
+
- `<criterion 2>`
|
|
25
|
+
- User-defined criteria:
|
|
26
|
+
- `<project-specific criterion>`
|
|
27
|
+
|
|
28
|
+
## Verification
|
|
29
|
+
|
|
30
|
+
- Required commands:
|
|
31
|
+
- `<command>` -> expected `<result>`
|
|
32
|
+
- Manual checks:
|
|
33
|
+
- `<check>`
|
|
34
|
+
|
|
35
|
+
## DEV Report
|
|
36
|
+
|
|
37
|
+
Return:
|
|
38
|
+
|
|
39
|
+
- files changed
|
|
40
|
+
- summary of implementation
|
|
41
|
+
- verification commands and results
|
|
42
|
+
- unmet criteria or blockers
|
|
43
|
+
- risks for QA/integration
|
|
44
|
+
|
|
45
|
+
## Boundaries
|
|
46
|
+
|
|
47
|
+
DEV implements only the assigned scope and does not QA-accept its own work.
|
|
48
|
+
|
|
49
|
+
## Minimal Filled Example
|
|
50
|
+
|
|
51
|
+
- Task: `Update one README paragraph to clarify install steps`
|
|
52
|
+
- Owner: `B/DEV-1`
|
|
53
|
+
- Write-allowed files: `README.md`
|
|
54
|
+
- Out of scope: `package code, release scripts, unrelated docs`
|
|
55
|
+
- Required command: `pnpm test` -> expected `passes`
|
|
56
|
+
- DEV report: `changed README.md; tests passed or blocker reported`
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# PM Role Template
|
|
2
|
+
|
|
3
|
+
Use this as a public starter template for an ODIN Sentinel PM role.
|
|
4
|
+
|
|
5
|
+
## Team Objective
|
|
6
|
+
|
|
7
|
+
- Outcome: `<desired result>`
|
|
8
|
+
- Deadline or session boundary: `<timebox>`
|
|
9
|
+
- Human operator: `<name or role>`
|
|
10
|
+
|
|
11
|
+
## Roles
|
|
12
|
+
|
|
13
|
+
- EXEC PM: owns launch/activation, readiness waivers or substitutions, CMUX
|
|
14
|
+
topology, staffing, final claim framing, and escalation.
|
|
15
|
+
- TEAM PM: owns routing and worker activation inside an assigned team after EXEC
|
|
16
|
+
PM launch. TEAM PM cannot staff new occupants, waive launch readiness, mutate
|
|
17
|
+
CMUX topology, or close lifecycle unless EXEC PM explicitly delegates it.
|
|
18
|
+
- ODIN: monitors health, scope, delivery, and drift; intervenes or escalates.
|
|
19
|
+
- DEV: implements assigned write scope only.
|
|
20
|
+
- QA: verifies independently and reports PASS/FAIL.
|
|
21
|
+
|
|
22
|
+
## Readiness Gates
|
|
23
|
+
|
|
24
|
+
- Node.js version satisfies package metadata: `<status>`
|
|
25
|
+
- ODIN MCP server configured and smoke-tested: `<status>`
|
|
26
|
+
- Native skill or prompt fallback available: `<status>`
|
|
27
|
+
- CMUX workspace and role slots ready: `<status>`
|
|
28
|
+
- Harness auth/account readiness checked: `<status>`
|
|
29
|
+
- Local inference smoke-tested if used: `<status>`
|
|
30
|
+
- Role compatibility smoke test passed: `<status>`
|
|
31
|
+
- Launch/waiver authority holder: `A/EXEC-PM`
|
|
32
|
+
|
|
33
|
+
## Operator-Friendly Status
|
|
34
|
+
|
|
35
|
+
- Plain-language summary: `<what is happening in one sentence>`
|
|
36
|
+
- Current blocker, if any: `<sign-in | permission prompt | API/account setup | local model smoke test | none>`
|
|
37
|
+
- Safe next choice for the human operator: `<approve | sign in | choose fallback harness | keep slot vacant | ask for help>`
|
|
38
|
+
- Secret-handling reminder: `Do not paste API keys or tokens into chat.`
|
|
39
|
+
|
|
40
|
+
## Assignments
|
|
41
|
+
|
|
42
|
+
- `<role slot>` -> `<agent/harness>` -> `<scope>`
|
|
43
|
+
|
|
44
|
+
## Blockers And Escalation
|
|
45
|
+
|
|
46
|
+
- Blocker: `<description>`
|
|
47
|
+
- Owner: `<role>`
|
|
48
|
+
- Escalate to human when: `<condition>`
|
|
49
|
+
|
|
50
|
+
## User-Defined Criteria
|
|
51
|
+
|
|
52
|
+
- `<project-specific criterion>`
|
|
53
|
+
|
|
54
|
+
## Minimal Filled Example
|
|
55
|
+
|
|
56
|
+
- Outcome: `Clarify install documentation for first-time users`
|
|
57
|
+
- Human operator: `project maintainer`
|
|
58
|
+
- Readiness summary: `MCP smoke test passes; Claude Code sign-in still needed`
|
|
59
|
+
- Safe next choice: `sign in to Claude Code, choose another harness, or keep that slot vacant`
|
|
60
|
+
- Assignment: `B/DEV-1 -> Codex -> README.md only`
|
|
61
|
+
- User-defined criterion: `Do not ask the operator to paste API keys`
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# QA Slice Template
|
|
2
|
+
|
|
3
|
+
Use this as a public starter template. Replace every placeholder before launch.
|
|
4
|
+
|
|
5
|
+
## Parent DEV Reference
|
|
6
|
+
|
|
7
|
+
- DEV task: `<task id or title>`
|
|
8
|
+
- DEV changed files: `<list supplied by DEV>`
|
|
9
|
+
- Expected verification: `<commands or checks>`
|
|
10
|
+
|
|
11
|
+
## Fresh-Context Review
|
|
12
|
+
|
|
13
|
+
QA starts from the task contract and changed files, not from DEV's confidence.
|
|
14
|
+
|
|
15
|
+
## Checks
|
|
16
|
+
|
|
17
|
+
- Acceptance criteria:
|
|
18
|
+
- `<criterion>` -> PASS/FAIL
|
|
19
|
+
- Scope compliance:
|
|
20
|
+
- Only declared write-allowed files changed: PASS/FAIL
|
|
21
|
+
- Security/privacy:
|
|
22
|
+
- No secrets printed or committed: PASS/FAIL
|
|
23
|
+
- No unsafe permission or auth behavior: PASS/FAIL
|
|
24
|
+
- Regression risk:
|
|
25
|
+
- Relevant tests or manual checks reproduced: PASS/FAIL
|
|
26
|
+
- User-defined criteria:
|
|
27
|
+
- `<project-specific criterion>` -> PASS/FAIL
|
|
28
|
+
|
|
29
|
+
## QA Verdict
|
|
30
|
+
|
|
31
|
+
Return one verdict:
|
|
32
|
+
|
|
33
|
+
- PASS: all required checks passed.
|
|
34
|
+
- FAIL: one or more required checks failed.
|
|
35
|
+
- BLOCKED: QA could not verify because required evidence, access, or commands are missing.
|
|
36
|
+
|
|
37
|
+
QA reports issues. QA does not fix code during the QA pass.
|
|
38
|
+
|
|
39
|
+
## Minimal Filled Example
|
|
40
|
+
|
|
41
|
+
- DEV task: `Update one README paragraph to clarify install steps`
|
|
42
|
+
- DEV changed files: `README.md`
|
|
43
|
+
- Expected verification: `pnpm test`
|
|
44
|
+
- Acceptance criterion: `README explains global and zero-install paths`
|
|
45
|
+
- Scope compliance: `Only README.md changed`
|
|
46
|
+
- Verdict: `PASS`, `FAIL`, or `BLOCKED` with one-sentence evidence
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
session_id: "<session-id>"
|
|
2
|
+
topology:
|
|
3
|
+
mode: "governed-team"
|
|
4
|
+
cmux_required: true
|
|
5
|
+
cmux_workspace: "<workspace-ref>"
|
|
6
|
+
layout: "human-readable spatial/pod organization"
|
|
7
|
+
executive_office:
|
|
8
|
+
- "A/EXEC-PM"
|
|
9
|
+
- "A/EXEC-ODIN"
|
|
10
|
+
- "A/EXEC-ASST"
|
|
11
|
+
development_pods:
|
|
12
|
+
- "B"
|
|
13
|
+
odin_mesh:
|
|
14
|
+
cadence_seconds: 30
|
|
15
|
+
executive_odin: "A/EXEC-ODIN"
|
|
16
|
+
team_odins:
|
|
17
|
+
- "B/ODIN"
|
|
18
|
+
model_profile:
|
|
19
|
+
minimum_compatible_child_mcp_version: "0.4.5"
|
|
20
|
+
inference_provider_inventory: []
|
|
21
|
+
handoff_sources:
|
|
22
|
+
- "<handoff path or none>"
|
|
23
|
+
startup_objectives:
|
|
24
|
+
- "<plain-language objective>"
|
|
25
|
+
role_slots:
|
|
26
|
+
- role_slot: "A/EXEC-PM"
|
|
27
|
+
harness: "<harness>"
|
|
28
|
+
readiness_status: "PASS"
|
|
29
|
+
layout_locator:
|
|
30
|
+
workspace: "<workspace-ref>"
|
|
31
|
+
pane: "<pane-ref>"
|
|
32
|
+
surface: "<surface-ref>"
|
|
33
|
+
scp_context_source: "native sentinel-coordination-protocol skill"
|
|
34
|
+
mcp_available: true
|
|
35
|
+
mcp_version: "0.4.5"
|
|
36
|
+
scp_skill_available: true
|
|
37
|
+
full_protocol_text_injected: false
|
|
38
|
+
watcher: "A/EXEC-ODIN"
|
|
39
|
+
- role_slot: "A/EXEC-ODIN"
|
|
40
|
+
harness: "<harness>"
|
|
41
|
+
readiness_status: "PASS"
|
|
42
|
+
layout_locator:
|
|
43
|
+
workspace: "<workspace-ref>"
|
|
44
|
+
pane: "<pane-ref>"
|
|
45
|
+
surface: "<surface-ref>"
|
|
46
|
+
scp_context_source: "native sentinel-coordination-protocol skill"
|
|
47
|
+
mcp_available: true
|
|
48
|
+
mcp_version: "0.4.5"
|
|
49
|
+
scp_skill_available: true
|
|
50
|
+
full_protocol_text_injected: false
|
|
51
|
+
watches:
|
|
52
|
+
- "A/EXEC-PM"
|
|
53
|
+
- "B/ODIN"
|
|
54
|
+
- role_slot: "B/ODIN"
|
|
55
|
+
harness: "<harness>"
|
|
56
|
+
readiness_status: "PASS"
|
|
57
|
+
layout_locator:
|
|
58
|
+
workspace: "<workspace-ref>"
|
|
59
|
+
pane: "<pane-ref>"
|
|
60
|
+
surface: "<surface-ref>"
|
|
61
|
+
scp_context_source: "native sentinel-coordination-protocol skill"
|
|
62
|
+
mcp_available: true
|
|
63
|
+
mcp_version: "0.4.5"
|
|
64
|
+
scp_skill_available: true
|
|
65
|
+
full_protocol_text_injected: false
|
|
66
|
+
watches:
|
|
67
|
+
- "B/TEAM-PM"
|
|
68
|
+
- "B/DEV-1"
|
|
69
|
+
- role_slot: "B/DEV-1"
|
|
70
|
+
harness: "<harness>"
|
|
71
|
+
readiness_status: "PASS"
|
|
72
|
+
layout_locator:
|
|
73
|
+
workspace: "<workspace-ref>"
|
|
74
|
+
pane: "<pane-ref>"
|
|
75
|
+
surface: "<surface-ref>"
|
|
76
|
+
scp_context_source: "native sentinel-coordination-protocol skill"
|
|
77
|
+
mcp_available: true
|
|
78
|
+
mcp_version: "0.4.5"
|
|
79
|
+
scp_skill_available: true
|
|
80
|
+
full_protocol_text_injected: false
|
|
81
|
+
watcher: "B/ODIN"
|
|
82
|
+
fallback_policy:
|
|
83
|
+
non_governed_one_shot_only: "Allowed only for bounded, non-persistent help when MCP/skill/protocol proof is absent. Not suitable for a persistent governed role."
|
|
84
|
+
operator_friendly_status:
|
|
85
|
+
summary: "<what is happening in one sentence>"
|
|
86
|
+
current_blocker: "<sign-in | permission prompt | API/account setup | local model smoke test | none>"
|
|
87
|
+
safe_next_choice: "<approve | sign in | choose fallback harness | keep slot vacant | ask for help>"
|
|
88
|
+
user_defined_criteria:
|
|
89
|
+
- "<project-specific criterion>"
|
|
90
|
+
|
|
91
|
+
# Minimal filled example
|
|
92
|
+
#
|
|
93
|
+
# session_id: "example-session"
|
|
94
|
+
# topology:
|
|
95
|
+
# mode: "governed-team"
|
|
96
|
+
# cmux_required: true
|
|
97
|
+
# cmux_workspace: "workspace:1"
|
|
98
|
+
# layout: "A executive office plus B development pod"
|
|
99
|
+
# executive_office:
|
|
100
|
+
# - "A/EXEC-PM"
|
|
101
|
+
# - "A/EXEC-ODIN"
|
|
102
|
+
# development_pods:
|
|
103
|
+
# - "B"
|
|
104
|
+
# odin_mesh:
|
|
105
|
+
# cadence_seconds: 30
|
|
106
|
+
# executive_odin: "A/EXEC-ODIN"
|
|
107
|
+
# team_odins:
|
|
108
|
+
# - "B/ODIN"
|
|
109
|
+
# model_profile:
|
|
110
|
+
# minimum_compatible_child_mcp_version: "0.4.5"
|
|
111
|
+
# inference_provider_inventory: []
|
|
112
|
+
# handoff_sources:
|
|
113
|
+
# - "none"
|
|
114
|
+
# startup_objectives:
|
|
115
|
+
# - "Clarify install documentation for first-time users"
|
|
116
|
+
# role_slots:
|
|
117
|
+
# - role_slot: "A/EXEC-PM"
|
|
118
|
+
# harness: "Codex"
|
|
119
|
+
# readiness_status: "PASS"
|
|
120
|
+
# layout_locator:
|
|
121
|
+
# workspace: "workspace:1"
|
|
122
|
+
# pane: "pane:1"
|
|
123
|
+
# surface: "surface:1"
|
|
124
|
+
# scp_context_source: "native sentinel-coordination-protocol skill"
|
|
125
|
+
# mcp_available: true
|
|
126
|
+
# mcp_version: "0.4.5"
|
|
127
|
+
# scp_skill_available: true
|
|
128
|
+
# full_protocol_text_injected: false
|
|
129
|
+
# watcher: "A/EXEC-ODIN"
|
|
130
|
+
# - role_slot: "B/ODIN"
|
|
131
|
+
# harness: "Codex"
|
|
132
|
+
# readiness_status: "PASS"
|
|
133
|
+
# layout_locator:
|
|
134
|
+
# workspace: "workspace:1"
|
|
135
|
+
# pane: "pane:2"
|
|
136
|
+
# surface: "surface:2"
|
|
137
|
+
# scp_context_source: "native sentinel-coordination-protocol skill"
|
|
138
|
+
# mcp_available: true
|
|
139
|
+
# mcp_version: "0.4.5"
|
|
140
|
+
# scp_skill_available: true
|
|
141
|
+
# full_protocol_text_injected: false
|
|
142
|
+
# watches:
|
|
143
|
+
# - "B/TEAM-PM"
|
|
144
|
+
# - "B/DEV-1"
|
|
145
|
+
# - role_slot: "B/DEV-1"
|
|
146
|
+
# harness: "Codex"
|
|
147
|
+
# readiness_status: "PASS"
|
|
148
|
+
# layout_locator:
|
|
149
|
+
# workspace: "workspace:1"
|
|
150
|
+
# pane: "pane:2"
|
|
151
|
+
# surface: "surface:3"
|
|
152
|
+
# scp_context_source: "odin-sentinel MCP at or above minimum version"
|
|
153
|
+
# mcp_available: true
|
|
154
|
+
# mcp_version: "0.4.5"
|
|
155
|
+
# scp_skill_available: false
|
|
156
|
+
# full_protocol_text_injected: false
|
|
157
|
+
# watcher: "B/ODIN"
|
|
158
|
+
# operator_friendly_status:
|
|
159
|
+
# summary: "Docs update is ready to dispatch."
|
|
160
|
+
# current_blocker: "none"
|
|
161
|
+
# safe_next_choice: "launch B/DEV-1"
|
|
162
|
+
# user_defined_criteria:
|
|
163
|
+
# - "Do not ask the operator to paste API keys."
|