@holmes-lab/holmes-kit 0.12.1 → 0.13.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/CHANGELOG.md +104 -0
- package/README.md +13 -4
- package/dist/.build-id +1 -1
- package/dist/holmes/cli/approve-context.js +10 -10
- package/dist/holmes/cli/approve-ref.js +5 -5
- package/dist/holmes/cli/approve-watch.d.ts +1 -1
- package/dist/holmes/cli/approve-watch.js +6 -6
- package/dist/holmes/cli/approve.d.ts +3 -3
- package/dist/holmes/cli/approve.js +57 -57
- package/dist/holmes/cli/autonomy.d.ts +22 -0
- package/dist/holmes/cli/autonomy.js +145 -0
- package/dist/holmes/cli/colophon.d.ts +6 -0
- package/dist/holmes/cli/colophon.js +24 -0
- package/dist/holmes/cli/doctor.d.ts +2 -2
- package/dist/holmes/cli/doctor.js +104 -87
- package/dist/holmes/cli/index.js +122 -63
- package/dist/holmes/cli/init.d.ts +2 -0
- package/dist/holmes/cli/init.js +31 -19
- package/dist/holmes/cli/interactive-prompt.d.ts +8 -0
- package/dist/holmes/cli/interactive-prompt.js +23 -0
- package/dist/holmes/cli/semantic-key.js +9 -9
- package/dist/holmes/cli/settings-merge.d.ts +2 -1
- package/dist/holmes/cli/settings-merge.js +15 -3
- package/dist/holmes/cli/upgrade.js +7 -7
- package/dist/holmes/cpg/proposed-content.js +2 -2
- package/dist/holmes/governance/autonomy.d.ts +9 -2
- package/dist/holmes/governance/autonomy.js +166 -5
- package/dist/holmes/guardrail/blind-spots.js +15 -15
- package/dist/holmes/hooks/pre-tool-use.js +111 -42
- package/dist/holmes/hooks/session-start.js +74 -34
- package/dist/holmes/hooks/stop.d.ts +1 -1
- package/dist/holmes/hooks/stop.js +12 -12
- package/dist/holmes/mcp/handlers.js +14 -1
- package/dist/holmes/mcp/server.js +19 -0
- package/dist/holmes/semantic/credentials.js +1 -1
- package/dist/holmes/spec/id-collision.js +2 -2
- package/dist/holmes/update/refresh.d.ts +49 -0
- package/dist/holmes/update/refresh.js +106 -0
- package/package.json +2 -2
- package/playbooks/publish/PLAYBOOK.md +47 -35
- package/playbooks/remediation/PLAYBOOK.md +1 -1
|
@@ -318,27 +318,27 @@ function evaluateStop(specs, evidence) {
|
|
|
318
318
|
// @implements A-SPEC-191 (§4a) — an existing-but-unreadable findings ledger is not a clean turn:
|
|
319
319
|
// it may hold an open critical, and a "clean" verdict here would also CLEAR standing ART-7 debt.
|
|
320
320
|
if (evidence?.findingsUnreadable) {
|
|
321
|
-
problems.push('[ART-7] findings
|
|
322
|
-
structured.push({ article: 'ART-7', detail: 'findings
|
|
321
|
+
problems.push('[ART-7] the findings ledger cannot be read — a turn that cannot confirm whether an open critical exists is not a clean turn (repair the ledger file\'s permissions/format)');
|
|
322
|
+
structured.push({ article: 'ART-7', detail: 'the findings ledger cannot be read' });
|
|
323
323
|
}
|
|
324
324
|
// @implements A-SPEC-452 — ART-1's second enforcement point. `undefined` is no signal and says
|
|
325
325
|
// nothing; an empty array is measured and clean. A file that claims nothing is a file the write
|
|
326
326
|
// gate never judged, which is exactly what the two-step generator bypass produces.
|
|
327
327
|
for (const file of evidence?.unanchoredChangedSources ?? []) {
|
|
328
|
-
const detail = `${file}:
|
|
328
|
+
const detail = `${file}: the changed source has no @implements anchor — it does not say what it implements (create an approved A-SPEC and add the anchor)`;
|
|
329
329
|
problems.push(`[ART-1] ${detail}`);
|
|
330
330
|
structured.push({ article: 'ART-1', detail });
|
|
331
331
|
}
|
|
332
332
|
// @implements A-SPEC-453 — ART-5's second enforcement point. The seal is forgeable; the record
|
|
333
333
|
// of the approving act is not.
|
|
334
334
|
for (const id of evidence?.unrecordedApprovals ?? []) {
|
|
335
|
-
const detail = `${id}:
|
|
335
|
+
const detail = `${id}: it is in the approved state but the ledger has no record of the approving act — a seal is a content hash and cannot prove itself (approve with spec_approve)`;
|
|
336
336
|
problems.push(`[ART-5] ${detail}`);
|
|
337
337
|
structured.push({ article: 'ART-5', detail });
|
|
338
338
|
}
|
|
339
339
|
// @implements A-SPEC-455
|
|
340
340
|
for (const file of evidence?.rolledBackLedgers ?? []) {
|
|
341
|
-
const detail = `${file}:
|
|
341
|
+
const detail = `${file}: the committed ledger history was rolled back — the working copy does not contain the committed copy as a prefix (the ledger is append-only; check with holmes-kit ledger rechain)`;
|
|
342
342
|
problems.push(`[ART-2] ${detail}`);
|
|
343
343
|
structured.push({ article: 'ART-2', detail });
|
|
344
344
|
}
|
|
@@ -440,18 +440,18 @@ function acknowledgeStop(violations, pending) {
|
|
|
440
440
|
*/
|
|
441
441
|
function describeTokenHealth(raw) {
|
|
442
442
|
if (raw === undefined || raw === '')
|
|
443
|
-
return 'HOLMES_APPROVAL
|
|
443
|
+
return 'no HOLMES_APPROVAL token (read-only — only actions that need approval are blocked)';
|
|
444
444
|
let parsed;
|
|
445
445
|
try {
|
|
446
446
|
parsed = JSON.parse(raw);
|
|
447
447
|
}
|
|
448
448
|
catch {
|
|
449
|
-
return 'HOLMES_APPROVAL
|
|
449
|
+
return 'HOLMES_APPROVAL invalid: JSON parse failed — actions that need approval are blocked';
|
|
450
450
|
}
|
|
451
451
|
if (!(0, risk_gate_1.isValidApproval)(parsed)) {
|
|
452
|
-
return 'HOLMES_APPROVAL
|
|
452
|
+
return 'HOLMES_APPROVAL invalid: missing required field(s) (actor/token/rationale) — actions that need approval are blocked';
|
|
453
453
|
}
|
|
454
|
-
return 'HOLMES_APPROVAL
|
|
454
|
+
return 'HOLMES_APPROVAL valid';
|
|
455
455
|
}
|
|
456
456
|
function decideStopGuard(wantsBlock, priorConsecutiveBlocks, cap = exports.MAX_CONSECUTIVE_BLOCKS) {
|
|
457
457
|
if (!wantsBlock)
|
|
@@ -523,7 +523,7 @@ function findProjectRoot(specsDir) {
|
|
|
523
523
|
if (r.marker !== 'given')
|
|
524
524
|
return r.root;
|
|
525
525
|
}
|
|
526
|
-
catch { /*
|
|
526
|
+
catch { /* if there is no marker, build nothing */ }
|
|
527
527
|
return null;
|
|
528
528
|
}
|
|
529
529
|
return null;
|
|
@@ -814,7 +814,7 @@ if (require.main === module) {
|
|
|
814
814
|
else if (ackWaiting.length > 0) {
|
|
815
815
|
// @implements A-SPEC-247 — acknowledged, not clean: name what is waiting so the user sees
|
|
816
816
|
// the standing approval debt exactly once, and the agent knows the ball is not in its court.
|
|
817
|
-
process.stderr.write(`[Holmes-Kit]
|
|
817
|
+
process.stderr.write(`[Holmes-Kit] awaiting approval — items waiting on the owner's decision: ${ackWaiting.join(', ')}. Decide them at ${(0, npx_bin_1.npxBin)()} holmes-kit approve. (This debt does not re-block.)\n`);
|
|
818
818
|
}
|
|
819
819
|
else if (guard.capped) {
|
|
820
820
|
process.stderr.write(`[Holmes-Kit] governance gate YIELDING after ${exports.MAX_CONSECUTIVE_BLOCKS} consecutive blocks — issues remain UNRESOLVED:\n${out.reason ?? ''}\n`);
|
|
@@ -826,7 +826,7 @@ if (require.main === module) {
|
|
|
826
826
|
}
|
|
827
827
|
catch (err) {
|
|
828
828
|
process.stderr.write(`[Holmes-Kit Stop Hook] Error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
829
|
-
process.exitCode = 0; // fail-open —
|
|
829
|
+
process.exitCode = 0; // fail-open — drain the pipe via exitCode, then exit naturally (avoids truncation)
|
|
830
830
|
}
|
|
831
831
|
});
|
|
832
832
|
}
|
|
@@ -1383,10 +1383,23 @@ function makeRawHandlers(store, opts) {
|
|
|
1383
1383
|
// itself. The switch is env-only and an agent cannot set it (pre-tool-use blocks that,
|
|
1384
1384
|
// A-SPEC-532.2). Off, or a hitl-classed spec, falls straight through to the elicitor
|
|
1385
1385
|
// unchanged — the autonomous-OFF path is byte-identical to before.
|
|
1386
|
-
|
|
1386
|
+
// @implements A-SPEC-553.1 — autonomy is the out-of-band env switch OR a valid, non-expired
|
|
1387
|
+
// session envelope marker under this project's `.ax/state/` (which an agent cannot write).
|
|
1388
|
+
const autonomyOn = (0, autonomy_1.autonomousApprovalEnabled)(process.env, a.root, new Date().toISOString());
|
|
1389
|
+
if (autonomyOn
|
|
1387
1390
|
&& (0, autonomy_1.specApprovalAutonomy)(target.spec, resolver([target.spec])) === 'auto') {
|
|
1388
1391
|
approveResolved = { approval: autonomousApproval(), source: 'autonomous' };
|
|
1389
1392
|
}
|
|
1393
|
+
else if (autonomyOn) {
|
|
1394
|
+
// @implements A-SPEC-551.1 — hitl-grade spec under autonomy: the in-session elicitation
|
|
1395
|
+
// dialog is auto-acceptable by an auto-mode client (the protocol cannot tell a human
|
|
1396
|
+
// from an auto-accept), so a governance-critical spec is never offered it. Do nothing
|
|
1397
|
+
// here — approveResolved stays undefined and the fail-closed refuse+enqueue path below
|
|
1398
|
+
// routes the act to the out-of-band human queue (holmes-kit approve). The A-SPEC-532.1
|
|
1399
|
+
// bound ("governance-critical specs never leave the human channel") thus becomes
|
|
1400
|
+
// ENFORCED, not aspirational. Autonomy OFF (the else) and the auto-grade branch above
|
|
1401
|
+
// stay byte-identical to before.
|
|
1402
|
+
}
|
|
1390
1403
|
else {
|
|
1391
1404
|
const resealing = typeof target.spec.frontmatter.approved_digest === 'string';
|
|
1392
1405
|
// The MODEL text is capped BEFORE the server markers are appended (round-2): a ~185+ char
|
|
@@ -75,6 +75,25 @@ const SERVER_INSTRUCTIONS = (() => {
|
|
|
75
75
|
}
|
|
76
76
|
})();
|
|
77
77
|
const server = new index_js_1.Server({ name: 'holmes-kit', version: PKG_VERSION }, { capabilities: { tools: {} }, instructions: SERVER_INSTRUCTIONS });
|
|
78
|
+
// @implements A-SPEC-547.2 — refresh the update cache from the MCP server too, not just Claude's
|
|
79
|
+
// SessionStart hook, so agy/codex (no SessionStart) keep the "new version" notice current. Detached,
|
|
80
|
+
// TTL-gated, fail-soft: any failure leaves the server starting normally.
|
|
81
|
+
(() => {
|
|
82
|
+
try {
|
|
83
|
+
const os = require('node:os');
|
|
84
|
+
const fs = require('node:fs');
|
|
85
|
+
const cp = require('node:child_process');
|
|
86
|
+
const { maybeSpawnRefresh } = require('../update/refresh');
|
|
87
|
+
maybeSpawnRefresh({
|
|
88
|
+
home: os.homedir(), env: process.env, now: Date.now(),
|
|
89
|
+
readFile: (p) => fs.readFileSync(p, 'utf8'),
|
|
90
|
+
execPath: process.execPath,
|
|
91
|
+
scriptPath: require('node:path').resolve(__dirname, '..', 'hooks', 'session-start.js'),
|
|
92
|
+
spawn: (cmd, args, o) => cp.spawn(cmd, args, o),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
catch { /* the update check is never a gate */ }
|
|
96
|
+
})();
|
|
78
97
|
const fullProfile = process.env.HOLMES_MCP_PROFILE === 'full';
|
|
79
98
|
// Register each handler as a tool with its real typed inputSchema so MCP
|
|
80
99
|
// clients can marshal complex (array/object) arguments; fall back to a
|
|
@@ -44,7 +44,7 @@ exports.removeSemanticKey = removeSemanticKey;
|
|
|
44
44
|
* an agent assigning the env names (self-granted egress, the HOLMES_ROLE class); this module owns
|
|
45
45
|
* the storage the human's CLI act writes to. A project-tree file is NEVER a source — a tree file
|
|
46
46
|
* is a commit-accident surface and readable by every in-session tool, which is exactly what the
|
|
47
|
-
* owner's ".env
|
|
47
|
+
* owner's ".env is a stopgap" call-out named.
|
|
48
48
|
*
|
|
49
49
|
* Resolution chain, the order being the contract:
|
|
50
50
|
* 1. HOLMES_SEMANTIC_API_KEY — dedicated name, CI/headless.
|
|
@@ -49,13 +49,13 @@ function detectIdCollisions(entries) {
|
|
|
49
49
|
sealContents.set(en.approvedDigest, new Set([en.contentDigest]));
|
|
50
50
|
}
|
|
51
51
|
const sharedSeals = [...sealContents.entries()].filter(([, cs]) => cs.size > 1).map(([s]) => s).sort();
|
|
52
|
-
const parts = [...byKey.entries()].sort().map(([k, ens]) => `${k}[seal=${[...new Set(ens.map((en) => en.approvedDigest ?? '
|
|
52
|
+
const parts = [...byKey.entries()].sort().map(([k, ens]) => `${k}[seal=${[...new Set(ens.map((en) => en.approvedDigest ?? 'none'))].sort().join('|')}] ← ${ens.map((en) => en.file).sort().join(', ')}`);
|
|
53
53
|
issues.push({
|
|
54
54
|
kind: 'id-collision',
|
|
55
55
|
id,
|
|
56
56
|
files: group.map((en) => en.file).sort(),
|
|
57
57
|
detail: parts.join(' / ')
|
|
58
|
-
+ (sharedSeals.length > 0 ? ` —
|
|
58
|
+
+ (sharedSeals.length > 0 ? ` — different contents share the same approved_digest (${sharedSeals.join(', ')}): suspected post-approval edit or seal copy` : ''),
|
|
59
59
|
});
|
|
60
60
|
}
|
|
61
61
|
// --- family-coexistence: a bare A/T-SPEC id alongside its own dot-suffix family ---
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { UpdateCache } from './update-notice';
|
|
2
|
+
/** The npm registry document for this scoped package — `dist-tags.latest` is the published latest. */
|
|
3
|
+
export declare const REGISTRY_URL = "https://registry.npmjs.org/@holmes-lab%2Fholmes-kit";
|
|
4
|
+
export interface RefreshOpts {
|
|
5
|
+
home: string;
|
|
6
|
+
now: number;
|
|
7
|
+
query: () => Promise<string>;
|
|
8
|
+
writeFile: (p: string, data: string) => void;
|
|
9
|
+
mkdir: (dir: string) => void;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* @implements A-SPEC-547.1
|
|
13
|
+
* Pure: the `dist-tags.latest` string from a registry document body, or null on broken JSON, a missing
|
|
14
|
+
* field, or a non-string value. Never throws.
|
|
15
|
+
*/
|
|
16
|
+
export declare function parseLatest(body: string): string | null;
|
|
17
|
+
/**
|
|
18
|
+
* @implements A-SPEC-547.1
|
|
19
|
+
* Query the registry (injected) and write ~/.holmes/update-check.json with {latest, checkedAt}. A query
|
|
20
|
+
* failure or an unparseable latest writes nothing and resolves false (fail-open). Staleness is the
|
|
21
|
+
* caller's decision — this always attempts.
|
|
22
|
+
*/
|
|
23
|
+
export declare function refreshCache(opts: RefreshOpts): Promise<boolean>;
|
|
24
|
+
export interface SpawnRefreshOpts {
|
|
25
|
+
home: string;
|
|
26
|
+
env: NodeJS.ProcessEnv;
|
|
27
|
+
now: number;
|
|
28
|
+
readFile: (p: string) => string;
|
|
29
|
+
execPath: string;
|
|
30
|
+
scriptPath: string;
|
|
31
|
+
spawn: (cmd: string, args: string[], opts: {
|
|
32
|
+
detached: boolean;
|
|
33
|
+
stdio: 'ignore';
|
|
34
|
+
}) => {
|
|
35
|
+
unref: () => void;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* @implements A-SPEC-547.2
|
|
40
|
+
* Pure: whether a network refresh should fire now — network allowed (not opted out / not CI) AND the
|
|
41
|
+
* cache is stale (or absent). Composes the two existing decisions so every trigger agrees.
|
|
42
|
+
*/
|
|
43
|
+
export declare function shouldRefreshCache(cached: UpdateCache | null, env: NodeJS.ProcessEnv, now: number): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* @implements A-SPEC-547.2
|
|
46
|
+
* Read the cache and, when shouldRefreshCache, spawn the detached `session-start.js --refresh` child so
|
|
47
|
+
* ANY harness's MCP startup keeps the cache fresh — not just Claude's SessionStart hook. Fail-open.
|
|
48
|
+
*/
|
|
49
|
+
export declare function maybeSpawnRefresh(opts: SpawnRefreshOpts): boolean;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.REGISTRY_URL = void 0;
|
|
37
|
+
exports.parseLatest = parseLatest;
|
|
38
|
+
exports.refreshCache = refreshCache;
|
|
39
|
+
exports.shouldRefreshCache = shouldRefreshCache;
|
|
40
|
+
exports.maybeSpawnRefresh = maybeSpawnRefresh;
|
|
41
|
+
// @implements A-SPEC-547.1
|
|
42
|
+
const path = __importStar(require("node:path"));
|
|
43
|
+
const update_notice_1 = require("./update-notice");
|
|
44
|
+
/** The npm registry document for this scoped package — `dist-tags.latest` is the published latest. */
|
|
45
|
+
exports.REGISTRY_URL = 'https://registry.npmjs.org/@holmes-lab%2Fholmes-kit';
|
|
46
|
+
/**
|
|
47
|
+
* @implements A-SPEC-547.1
|
|
48
|
+
* Pure: the `dist-tags.latest` string from a registry document body, or null on broken JSON, a missing
|
|
49
|
+
* field, or a non-string value. Never throws.
|
|
50
|
+
*/
|
|
51
|
+
function parseLatest(body) {
|
|
52
|
+
try {
|
|
53
|
+
const latest = JSON.parse(body)?.['dist-tags']?.latest;
|
|
54
|
+
return typeof latest === 'string' ? latest : null;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* @implements A-SPEC-547.1
|
|
62
|
+
* Query the registry (injected) and write ~/.holmes/update-check.json with {latest, checkedAt}. A query
|
|
63
|
+
* failure or an unparseable latest writes nothing and resolves false (fail-open). Staleness is the
|
|
64
|
+
* caller's decision — this always attempts.
|
|
65
|
+
*/
|
|
66
|
+
async function refreshCache(opts) {
|
|
67
|
+
let latest = null;
|
|
68
|
+
try {
|
|
69
|
+
latest = parseLatest(await opts.query());
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return false; // fail-open: a network/query failure writes nothing
|
|
73
|
+
}
|
|
74
|
+
if (!latest)
|
|
75
|
+
return false;
|
|
76
|
+
const dir = path.join(opts.home, '.holmes');
|
|
77
|
+
opts.mkdir(dir);
|
|
78
|
+
opts.writeFile(path.join(dir, 'update-check.json'), JSON.stringify({ latest, checkedAt: opts.now }));
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* @implements A-SPEC-547.2
|
|
83
|
+
* Pure: whether a network refresh should fire now — network allowed (not opted out / not CI) AND the
|
|
84
|
+
* cache is stale (or absent). Composes the two existing decisions so every trigger agrees.
|
|
85
|
+
*/
|
|
86
|
+
function shouldRefreshCache(cached, env, now) {
|
|
87
|
+
return (0, update_notice_1.shouldQuery)(env) && (0, update_notice_1.cacheIsStale)(cached, now);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* @implements A-SPEC-547.2
|
|
91
|
+
* Read the cache and, when shouldRefreshCache, spawn the detached `session-start.js --refresh` child so
|
|
92
|
+
* ANY harness's MCP startup keeps the cache fresh — not just Claude's SessionStart hook. Fail-open.
|
|
93
|
+
*/
|
|
94
|
+
function maybeSpawnRefresh(opts) {
|
|
95
|
+
try {
|
|
96
|
+
const cached = (0, update_notice_1.readCache)(opts.home, opts.readFile);
|
|
97
|
+
if (!shouldRefreshCache(cached, opts.env, opts.now))
|
|
98
|
+
return false;
|
|
99
|
+
const child = opts.spawn(opts.execPath, [opts.scriptPath, '--refresh'], { detached: true, stdio: 'ignore' });
|
|
100
|
+
child.unref();
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return false; // fail-open: a refresh is never a gate
|
|
105
|
+
}
|
|
106
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"//": "@implements A-SPEC-209",
|
|
3
3
|
"name": "@holmes-lab/holmes-kit",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.13.0",
|
|
5
5
|
"description": "Holmes-Kit — deterministic Agentic Software Engineering (ASE) harness with causal traceability (spec chain + D-CPG + RTM + phase guardrail)",
|
|
6
6
|
"main": "dist/holmes/mcp/server.js",
|
|
7
7
|
"types": "dist/holmes/mcp/server.d.ts",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"mcp",
|
|
43
43
|
"guardrail"
|
|
44
44
|
],
|
|
45
|
-
"author": "SungNam Park <
|
|
45
|
+
"author": "SungNam Park <sungnam.park.korea@gmail.com>",
|
|
46
46
|
"license": "MIT",
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/better-sqlite3": "^7.6.13",
|
|
@@ -2,72 +2,85 @@
|
|
|
2
2
|
name: holmes-publish
|
|
3
3
|
description: >-
|
|
4
4
|
Use when asked to publish holmes-kit to NPM registry or run release workflow governed by
|
|
5
|
-
"HOLMES_APPROVAL", "hard-hitl", or "A-SPEC-133"
|
|
6
|
-
|
|
5
|
+
"HOLMES_APPROVAL", "hard-hitl", or "A-SPEC-133" — the repository owner authorizes an
|
|
6
|
+
irreversible / high-risk release Human-In-The-Loop, while a low-risk release may self-publish
|
|
7
|
+
under autonomous mode.
|
|
7
8
|
---
|
|
8
9
|
|
|
9
|
-
# holmes-publish — NPM
|
|
10
|
+
# holmes-publish — NPM 배포 및 릴리스 자율/HITL 절차
|
|
10
11
|
|
|
11
|
-
이 플레이북은 `@holmes-lab/holmes-kit`
|
|
12
|
-
|
|
12
|
+
이 플레이북은 `@holmes-lab/holmes-kit` 를 NPM Registry에 안전하게 배포하는 **규정 절차**를 정의합니다.
|
|
13
|
+
npm publish 는 **비가역·외부노출**이라 기본은 HITL(사람 승인)이지만, **저위험 릴리스**(문서·테스트·양성
|
|
14
|
+
내부 변경의 patch/minor)는 **자율 모드에서 자율 처리**할 수 있고, **비가역/high-risk**(게이트·보안·아키텍처
|
|
15
|
+
변경, major 범프, 상위 스펙)는 반드시 **오너의 대역외 HITL 승인**을 거칩니다. 판정은 결정론적 분류기
|
|
16
|
+
`releaseAutonomy`(A-SPEC-555.1, `governance/autonomy.ts`)가 담당합니다.
|
|
13
17
|
|
|
14
18
|
---
|
|
15
19
|
|
|
16
|
-
## 배포
|
|
20
|
+
## 배포 절차 (Release Workflow)
|
|
17
21
|
|
|
18
22
|
### 1단계: 사전 품질 감사 (Pre-flight Quality Audit)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
3. `npm run build`
|
|
23
|
+
1. `npm run typecheck` (타입 오류 0건)
|
|
24
|
+
2. 전체 스위트 green — `mcp__holmes-kit__test_run`(HEAD~1..HEAD) 또는 `npx jest`. 부하가 높으면 대시보드
|
|
25
|
+
canary(A-SPEC-435)가 타임아웃할 수 있으니 부하를 낮춘 뒤 재실행한다(코드가 아니라 부하 문제).
|
|
26
|
+
3. `npm run build` (`dist/` 최신 + `.build-id`).
|
|
23
27
|
|
|
24
28
|
> [!IMPORTANT]
|
|
25
|
-
>
|
|
29
|
+
> 타입 오류나 실제(부하-무관) 테스트 실패가 하나라도 있으면 즉시 배포 중단(Abort).
|
|
26
30
|
|
|
27
31
|
---
|
|
28
32
|
|
|
29
33
|
### 2단계: 패키지 타르볼 시뮬레이션 및 검수 (Tarball Inspection)
|
|
30
|
-
|
|
31
|
-
- `
|
|
32
|
-
- 출력된 타르볼 패키지 목록 검수:
|
|
33
|
-
- `package.json` (`@holmes-lab/holmes-kit` 명칭 및 버전 확인)
|
|
34
|
-
- `bin/` (`holmes-kit.js`, `holmes-mcp.js`, `holmes-hook-antigravity.js` 등)
|
|
35
|
-
- `dist/` (전체 컴파일 산출물)
|
|
36
|
-
- `playbooks/` (`adopt`, `author-slice`, `promote-slice`, `publish`)
|
|
37
|
-
- `CHANGELOG.md`, `README.md`
|
|
34
|
+
- `npm publish --dry-run`
|
|
35
|
+
- 타르볼 구성 검수: `package.json`(명칭·버전), `bin/`, `dist/`, `playbooks/`, `CHANGELOG.md`, `README.md`.
|
|
38
36
|
|
|
39
37
|
---
|
|
40
38
|
|
|
41
|
-
###
|
|
42
|
-
|
|
39
|
+
### 2.5단계: 문서 정합성 게이트 (Docs Currency Gate) — 배포는 정직한 고지다
|
|
40
|
+
타르볼에 문서가 **포함**됐는지가 아니라 **최신인지**를 diff로 검사한다(이 단계 없이 폐기된 동작이 README에
|
|
41
|
+
현재형으로 남는 사고가 실제로 있었다):
|
|
42
|
+
1. 직전 릴리스 태그 이후 승인된 스펙 열거: `git log <last-tag>..HEAD --name-only -- .ax/specs/03_a-spec/`.
|
|
43
|
+
2. 각 A-SPEC 중 **사용자-대면**(CLI 명령/플래그, 동작 변경, env 스위치, 게이트 행동)인 것마다:
|
|
44
|
+
- `CHANGELOG.md` 의 이번 버전 항목이 그 변화를 기술하는가.
|
|
45
|
+
- `README.md` 의 기능 목록/CLI 치트시트가 새 명령·플래그를 담고, **폐기된 동작을 현재형으로 서술하지
|
|
46
|
+
않는가**(바뀐 동작의 옛 문구를 `grep` 으로 점검).
|
|
47
|
+
> [!CAUTION]
|
|
48
|
+
> 사용자-대면 변화가 CHANGELOG/README 에 반영되지 않았으면 배포 중단 — 문서 drift 는 거짓 주장이다.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
### 3단계: 릴리스 자율 판정 (Release Autonomy) — auto 또는 HITL
|
|
53
|
+
`releaseAutonomy(specsSinceTag, versionBump, process.env, root, now)` 로 이번 릴리스를 분류한다
|
|
54
|
+
(`versionBumpKind(현재버전, 대상버전)` 으로 범프 종류 산출):
|
|
55
|
+
- **`'auto'`** (자율 ON + 모든 스펙 auto 등급 + patch/minor): 사람 없이 4단계로 진행하되, 원장에 배포
|
|
56
|
+
근거를 남긴다(무엇을·왜·어느 버전). 저위험 릴리스의 자율 처리.
|
|
57
|
+
- **`'hitl'`** (자율 OFF · major 범프 · 게이트/보안/아키텍처 스펙 · 상위 REQ/H/C · 비가역 breaking_change):
|
|
58
|
+
**오너에게 배포 요약을 보고하고 명시적 대역외 승인**을 받은 뒤에만 진행한다. AI Agent 는 독단적으로
|
|
59
|
+
`npm publish` 하지 않는다. 승인은 채팅의 명시적 확인 또는 `HOLMES_APPROVAL` 로 전달된다.
|
|
43
60
|
|
|
44
|
-
**[보고 양식]**:
|
|
45
|
-
|
|
46
|
-
- **대상 버전**: `vX.Y.Z`
|
|
47
|
-
- **테스트 결과**: PASS (100%)
|
|
48
|
-
- **타르볼 파일 수 및 용량**: N개 / XXX kB
|
|
61
|
+
**[HITL 보고 양식]**: 패키지 `@holmes-lab/holmes-kit` · 대상 `vX.Y.Z`(범프: major/minor/patch) ·
|
|
62
|
+
테스트 PASS · 타르볼 N개/XXX kB · 분류 사유(어느 스펙이 hitl 인지).
|
|
49
63
|
|
|
50
64
|
> [!CAUTION]
|
|
51
|
-
>
|
|
52
|
-
> 승인은 채팅을 통한 명시적 확인 또는 `HOLMES_APPROVAL` 환경변수를 통해 전달됩니다.
|
|
65
|
+
> hitl 판정에서 오너 승인이 없으면 즉시 중단. 자율(auto) 판정이라도 2.5단계 문서 게이트를 통과해야 한다.
|
|
53
66
|
|
|
54
67
|
---
|
|
55
68
|
|
|
56
69
|
### 4단계: NPM 게시 실행 (NPM Publishing Execution)
|
|
57
|
-
승인이 완료된 경우에만 실제 게시 명령을 수행합니다:
|
|
58
70
|
```bash
|
|
59
71
|
npm publish --access public
|
|
60
72
|
```
|
|
61
|
-
- 2FA / Web OTP
|
|
73
|
+
- 2FA / Web OTP 가 필요하면 인증 URL 을 사용자에게 제공하고 대기.
|
|
74
|
+
- verify-release 가 전체 `npm test` 를 돌려 플레이크할 수 있으므로, 1단계를 이미 green 으로 통과했다면
|
|
75
|
+
`npm publish --ignore-scripts` + 수동 무결성 확인(트리 클린·build-id==HEAD·CHANGELOG 항목)로 우회할 수 있다.
|
|
62
76
|
|
|
63
77
|
---
|
|
64
78
|
|
|
65
79
|
### 5단계: 배포 후 검증 및 Git 태깅 (Post-Release Verification)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
2. Git 커밋 및 태그 생성:
|
|
80
|
+
1. NPM 반영 확인: `npm view @holmes-lab/holmes-kit version` (레지스트리 read-cache 지연 시 레지스트리 JSON 직접 조회).
|
|
81
|
+
2. 커밋 및 태그:
|
|
69
82
|
```bash
|
|
70
|
-
git add package.json
|
|
83
|
+
git add package.json CHANGELOG.md README.md
|
|
71
84
|
git commit -m "chore: release vX.Y.Z"
|
|
72
85
|
git tag -a vX.Y.Z -m "vX.Y.Z Release"
|
|
73
86
|
```
|
|
@@ -75,7 +88,6 @@ npm publish --access public
|
|
|
75
88
|
---
|
|
76
89
|
|
|
77
90
|
## 플레이북 트리거 조건
|
|
78
|
-
다음 요청 시 자동 트리거됩니다:
|
|
79
91
|
- "npm publish"
|
|
80
92
|
- "release to npm"
|
|
81
93
|
- "HOLMES_APPROVAL"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: holmes-remediation
|
|
3
3
|
description: >-
|
|
4
|
-
Automatically triggered when Holmes-Kit pre-tool-use hook gate denies a tool call with "
|
|
4
|
+
Automatically triggered when Holmes-Kit pre-tool-use hook gate denies a tool call with "Target specification (...) is not approved" or missing code anchor. Guides the agent to execute 1-call spec_remediate or 3-step recovery workflow.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Holmes-Kit Remediation Playbook (Self-Healing Recovery)
|