@sabaiway/agent-workflow-kit 2.1.0 → 3.0.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 +45 -0
- package/README.md +6 -5
- package/SKILL.md +12 -8
- package/bin/install.mjs +1 -1
- package/bridges/antigravity-cli-bridge/SKILL.md +8 -1
- package/bridges/antigravity-cli-bridge/bin/agy-review-honesty.test.mjs +150 -0
- package/bridges/antigravity-cli-bridge/bin/agy-review-model-screen.test.mjs +76 -0
- package/bridges/antigravity-cli-bridge/bin/agy-review.sh +80 -15
- package/bridges/antigravity-cli-bridge/bin/agy-review.test.mjs +95 -4
- package/bridges/antigravity-cli-bridge/capability.json +3 -2
- package/bridges/codex-cli-bridge/SKILL.md +8 -1
- package/bridges/codex-cli-bridge/bin/codex-review-honesty.test.mjs +143 -0
- package/bridges/codex-cli-bridge/bin/codex-review.sh +71 -10
- package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +93 -10
- package/bridges/codex-cli-bridge/capability.json +3 -2
- package/capability.json +1 -1
- package/migrations/3.0.0-hardened-core-loop.md +44 -0
- package/migrations/README.md +1 -1
- package/package.json +2 -2
- package/references/hooks/gate-approve.mjs +1 -1
- package/references/modes/bootstrap.md +3 -3
- package/references/modes/commit-guard.md +19 -0
- package/references/modes/core-evidence.md +15 -0
- package/references/modes/coverage-check.md +12 -0
- package/references/modes/doc-parity.md +7 -6
- package/references/modes/gates.md +7 -6
- package/references/modes/grounding.md +1 -2
- package/references/modes/hook.md +1 -1
- package/references/modes/review-state.md +3 -3
- package/references/modes/upgrade.md +6 -6
- package/references/modes/velocity.md +2 -2
- package/references/scripts/archive-decisions.mjs +1 -1
- package/references/scripts/install-git-hooks-repo-exec.test.mjs +82 -0
- package/references/scripts/install-git-hooks.mjs +90 -18
- package/references/scripts/install-git-hooks.test.mjs +102 -0
- package/references/scripts/migrate-gates-branches.test.mjs +157 -0
- package/references/scripts/migrate-gates.mjs +395 -0
- package/references/scripts/migrate-gates.test.mjs +284 -0
- package/references/templates/agent_rules.md +2 -2
- package/tools/ack-write.mjs +1 -1
- package/tools/atomic-write.mjs +2 -2
- package/tools/autonomy-config.mjs +1 -1
- package/tools/autonomy-doctor.mjs +1 -1
- package/tools/autonomy-write.mjs +1 -1
- package/tools/bridge-settings-read.mjs +1 -1
- package/tools/bridge-settings.mjs +1 -1
- package/tools/changed-surface.mjs +8 -69
- package/tools/cheap-agents.mjs +2 -2
- package/tools/commands.mjs +17 -10
- package/tools/commit-guard.mjs +167 -0
- package/tools/core-evidence.mjs +914 -0
- package/tools/coverage-check.mjs +260 -0
- package/tools/delegation.mjs +1 -1
- package/tools/detect-backends.mjs +3 -3
- package/tools/doc-parity.mjs +11 -27
- package/tools/engine-source.mjs +1 -1
- package/tools/family-members.mjs +1 -1
- package/tools/family-registry.mjs +1 -1
- package/tools/fs-safe.mjs +1 -1
- package/tools/gate-hook.mjs +1 -1
- package/tools/{seed-gates.mjs → gates-init.mjs} +58 -52
- package/tools/grounding.mjs +6 -83
- package/tools/hide-footprint.mjs +1 -1
- package/tools/inject-methodology.mjs +1 -1
- package/tools/known-footprint.mjs +1 -1
- package/tools/labels.mjs +1 -1
- package/tools/lcov.mjs +6 -10
- package/tools/lens-region.mjs +1 -1
- package/tools/manifest/validate.mjs +19 -0
- package/tools/migrate-adr-store.mjs +2 -2
- package/tools/orchestration-config.mjs +1 -1
- package/tools/orchestration-write.mjs +1 -1
- package/tools/presentation.mjs +1 -1
- package/tools/procedures.mjs +2 -2
- package/tools/recipes.mjs +60 -9
- package/tools/recommendations.mjs +63 -7
- package/tools/release-scan.mjs +1 -1
- package/tools/renderers.mjs +1 -1
- package/tools/review-state.mjs +288 -340
- package/tools/run-gates.mjs +216 -92
- package/tools/sandbox-masks.mjs +1 -1
- package/tools/semver-lite.mjs +1 -1
- package/tools/set-autonomy.mjs +1 -1
- package/tools/set-recipe.mjs +1 -1
- package/tools/setup-backends.mjs +1 -1
- package/tools/surface.mjs +1 -1
- package/tools/uninstall.mjs +1 -1
- package/tools/velocity-profile.mjs +2 -2
- package/tools/view-model.mjs +1 -1
- package/references/modes/fold-completeness.md +0 -30
- package/references/modes/review-ledger.md +0 -34
- package/references/templates/verification-profile.json +0 -10
- package/tools/fold-completeness-run.mjs +0 -1120
- package/tools/fold-completeness.mjs +0 -672
- package/tools/review-ledger-core.mjs +0 -428
- package/tools/review-ledger-write.mjs +0 -647
- package/tools/review-ledger.mjs +0 -630
- package/tools/sarif.mjs +0 -52
- package/tools/verification-profile.mjs +0 -219
package/tools/recipes.mjs
CHANGED
|
@@ -12,14 +12,14 @@
|
|
|
12
12
|
// Invariants (the backends/status posture): pure (no fs/network/CLI in planRecipe/recommendRecipe),
|
|
13
13
|
// read-only, NEVER runs a subscription CLI. The kit only surfaces/selects/plans a recipe — the
|
|
14
14
|
// orchestrator (the main agent) executes it via the bridge skills and always makes the single commit;
|
|
15
|
-
// a backend is advisory or delegated, never autonomous. Dependency-free, Node >=
|
|
15
|
+
// a backend is advisory or delegated, never autonomous. Dependency-free, Node >= 22.
|
|
16
16
|
|
|
17
|
-
import { existsSync } from 'node:fs';
|
|
17
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
18
18
|
import { dirname, join, resolve } from 'node:path';
|
|
19
19
|
import { pathToFileURL } from 'node:url';
|
|
20
20
|
// The host-level bridge-settings snapshot (fact-only, best-effort). READ-ONLY core only — never the
|
|
21
21
|
// writer — so this read-only advisor never pulls in the atomic-write core.
|
|
22
|
-
import { settingsSnapshot } from './bridge-settings-read.mjs';
|
|
22
|
+
import { settingsSnapshot, DEFAULT_BUNDLE_ROOT } from './bridge-settings-read.mjs';
|
|
23
23
|
import {
|
|
24
24
|
detectBackends,
|
|
25
25
|
wrapperCmdFor,
|
|
@@ -298,7 +298,50 @@ export const resolveActivityRecipe = ({ config = {}, readiness = [], activity, s
|
|
|
298
298
|
// DISPLAY_ALIASES — the ONE alias table the recommendation clause already uses; ordering is the
|
|
299
299
|
// deterministic BACKEND_PRIORITY (codex before agy), independent of detection emission order.
|
|
300
300
|
// Always exactly one line: no part may carry a newline (pinned by tests).
|
|
301
|
-
|
|
301
|
+
// composeConfiguredPosture(ctx) → the CONFIGURED dispatch posture (strip-the-kit D5), rendered
|
|
302
|
+
// from the bundled manifests' VALIDATED posture pins overlaid with the ACTIVE bridge-settings —
|
|
303
|
+
// today only the codex tier knob (bridge-settings stays model/effort-free by design, so this
|
|
304
|
+
// surface can never carry a settings-file model claim). Returns null when NO bundled bridge
|
|
305
|
+
// declares a posture block (a pre-D5 bundle keeps every surface byte-identical). Best-effort:
|
|
306
|
+
// a corrupt bundle degrades to null — the posture segment is a footer fact, never a gate.
|
|
307
|
+
export const composeConfiguredPosture = (ctx = {}) => {
|
|
308
|
+
try {
|
|
309
|
+
const bundleRoot = ctx.bundleRoot ?? DEFAULT_BUNDLE_ROOT;
|
|
310
|
+
const read = ctx.readFile ?? readFileSync;
|
|
311
|
+
const parts = [];
|
|
312
|
+
for (const name of BACKEND_PRIORITY) {
|
|
313
|
+
let manifest;
|
|
314
|
+
try {
|
|
315
|
+
manifest = JSON.parse(String(read(join(bundleRoot, name, 'capability.json'), 'utf8')));
|
|
316
|
+
} catch {
|
|
317
|
+
return null; // an unreadable/corrupt manifest is CORRUPTION — a partial posture would lie
|
|
318
|
+
}
|
|
319
|
+
if (!Object.hasOwn(manifest, 'posture')) continue; // a pre-D5 bridge — legitimate absence
|
|
320
|
+
const posture = manifest.posture;
|
|
321
|
+
// The FULL closed shape (the validator/receipt-predicate twin): a present-but-invalid
|
|
322
|
+
// block nulls the WHOLE render — fail closed to silence, never a partial/mangled line.
|
|
323
|
+
const invalid =
|
|
324
|
+
posture === null || typeof posture !== 'object' || Array.isArray(posture) ||
|
|
325
|
+
typeof posture.model !== 'string' || posture.model.length === 0 ||
|
|
326
|
+
(Object.hasOwn(posture, 'effort') && (typeof posture.effort !== 'string' || posture.effort.length === 0)) ||
|
|
327
|
+
(Object.hasOwn(posture, 'tier') && posture.tier !== null && (typeof posture.tier !== 'string' || posture.tier.length === 0)) ||
|
|
328
|
+
Object.keys(posture).some((k) => !['model', 'effort', 'tier'].includes(k));
|
|
329
|
+
if (invalid) return null;
|
|
330
|
+
const seg = [`model=${posture.model}`];
|
|
331
|
+
if (Object.hasOwn(posture, 'effort')) seg.push(`effort=${posture.effort}`);
|
|
332
|
+
if (Object.hasOwn(posture, 'tier')) {
|
|
333
|
+
const knob = (ctx.settings?.active ?? []).find((s) => s.key === 'CODEX_SERVICE_TIER' && s.bridge === name);
|
|
334
|
+
seg.push(knob ? `tier=${knob.value} (bridge-settings)` : `tier=${posture.tier ?? 'standard'}`);
|
|
335
|
+
}
|
|
336
|
+
parts.push(`${DISPLAY_ALIASES[name] ?? name} ${seg.join(' ')}`);
|
|
337
|
+
}
|
|
338
|
+
return parts.length ? parts.join(' · ') : null;
|
|
339
|
+
} catch {
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
export const composeStatusLine = (detection, recommendation, settings = null, autonomy = null, posture = null) => {
|
|
302
345
|
const backends = [...detection]
|
|
303
346
|
.sort((a, b) => priorityIndex(a.name) - priorityIndex(b.name))
|
|
304
347
|
.map((b) => `${DISPLAY_ALIASES[b.name] ?? b.name} ${b.readiness === READY ? '✓' : '✗'} ${b.readiness}`)
|
|
@@ -315,7 +358,10 @@ export const composeStatusLine = (detection, recommendation, settings = null, au
|
|
|
315
358
|
// suffix precedent). Fact-only: effective per-activity levels + the render-sync state; an absent
|
|
316
359
|
// policy says "computed defaults" honestly; a malformed policy surfaces LOUDLY, never omitted.
|
|
317
360
|
const autonomySegment = autonomy == null ? '' : ` · autonomy: ${oneLine(formatAutonomySegment(autonomy))}`;
|
|
318
|
-
|
|
361
|
+
// The D5 posture segment: rendered ONLY when the caller supplies the composed pins-derived
|
|
362
|
+
// posture (composeConfiguredPosture) — an omitted/null param keeps the line byte-identical.
|
|
363
|
+
const postureSegment = posture == null ? '' : ` · posture: ${oneLine(posture)}`;
|
|
364
|
+
return base + suffix + autonomySegment + postureSegment;
|
|
319
365
|
};
|
|
320
366
|
|
|
321
367
|
// The one-segment autonomy renderer behind composeStatusLine's 4th param.
|
|
@@ -425,7 +471,7 @@ export const composeActiveRecipeLine = ({ config, source } = {}, detection, auto
|
|
|
425
471
|
|
|
426
472
|
// The structured report behind `--json` — the recipes, the recommendation, a plan per recipe, and
|
|
427
473
|
// (additive) the pasteable one-line backend status composed from the same detection.
|
|
428
|
-
export const buildReport = (detection, settings = null, autonomy = null) => {
|
|
474
|
+
export const buildReport = (detection, settings = null, autonomy = null, posture = null) => {
|
|
429
475
|
const recommendation = recommendRecipe(detection);
|
|
430
476
|
return {
|
|
431
477
|
recipes: RECIPES.map(({ id, title, role, minBackends, degradesTo, summary }) => ({
|
|
@@ -440,7 +486,7 @@ export const buildReport = (detection, settings = null, autonomy = null) => {
|
|
|
440
486
|
plans: RECIPES.map((r) => planRecipe(r.id, detection)),
|
|
441
487
|
// The SAME autonomy facts the --status-line surface renders — the --json envelope must never
|
|
442
488
|
// expose a stale machine-composed status line (codex R1, Segment B).
|
|
443
|
-
statusLine: composeStatusLine(detection, recommendation, settings, autonomy),
|
|
489
|
+
statusLine: composeStatusLine(detection, recommendation, settings, autonomy, posture),
|
|
444
490
|
};
|
|
445
491
|
};
|
|
446
492
|
|
|
@@ -512,8 +558,13 @@ exclusive. Detection only — never writes, never commits, never runs a subscrip
|
|
|
512
558
|
console.error(`[agent-workflow-kit] ${err.message}`);
|
|
513
559
|
return err.exitCode ?? 1;
|
|
514
560
|
}
|
|
515
|
-
} else if (argv.includes('--status-line'))
|
|
516
|
-
|
|
561
|
+
} else if (argv.includes('--status-line')) {
|
|
562
|
+
const snapshot = settingsSnapshot();
|
|
563
|
+
console.log(composeStatusLine(detection, recommendRecipe(detection), snapshot, await composeAutonomyFacts(process.cwd()), composeConfiguredPosture({ settings: snapshot })));
|
|
564
|
+
} else if (argv.includes('--json')) {
|
|
565
|
+
const snapshot = settingsSnapshot();
|
|
566
|
+
console.log(JSON.stringify(buildReport(detection, snapshot, await composeAutonomyFacts(process.cwd()), composeConfiguredPosture({ settings: snapshot })), null, 2));
|
|
567
|
+
}
|
|
517
568
|
else console.log(formatRecipes(detection));
|
|
518
569
|
return 0;
|
|
519
570
|
};
|
|
@@ -28,9 +28,9 @@
|
|
|
28
28
|
// Read-only: never writes, never commits, never runs a subscription CLI. The reused probes are all
|
|
29
29
|
// exported read-only surfaces of their owning tools (velocity/autonomy/doctor/backends/recipes/
|
|
30
30
|
// registry/sandbox-masks); the sandbox-masks and settings probes may run read-only git queries.
|
|
31
|
-
// Dependency-free, Node >=
|
|
31
|
+
// Dependency-free, Node >= 22. No side effects on import (the isDirectRun idiom).
|
|
32
32
|
|
|
33
|
-
import { readFileSync, readdirSync, lstatSync } from 'node:fs';
|
|
33
|
+
import { readFileSync, readdirSync, lstatSync, existsSync } from 'node:fs';
|
|
34
34
|
import { createHash } from 'node:crypto';
|
|
35
35
|
import { homedir } from 'node:os';
|
|
36
36
|
import { dirname, join, resolve } from 'node:path';
|
|
@@ -54,6 +54,8 @@ import { ACTIVITIES, resolveActivityRecipe } from './recipes.mjs';
|
|
|
54
54
|
import { surveyFamily, surveyGateHook } from './family-registry.mjs';
|
|
55
55
|
import { probeSandboxMasks, needsMasksApply } from './sandbox-masks.mjs';
|
|
56
56
|
import { shellQuoteArg } from './review-state.mjs';
|
|
57
|
+
import { isFinalCapableDeclaration } from './run-gates.mjs';
|
|
58
|
+
import { resolveGitHooksPath } from './commit-guard.mjs';
|
|
57
59
|
import { loadConfig } from './orchestration-config.mjs';
|
|
58
60
|
import { DEFAULT_BUNDLE_ROOT, SETTINGS_FILENAME, settingsPath, parseSettings, duplicateKeys } from './bridge-settings-read.mjs';
|
|
59
61
|
import { assertContainedRealPath } from './fs-safe.mjs';
|
|
@@ -97,6 +99,7 @@ export const SEVERITIES = Object.freeze({
|
|
|
97
99
|
'review-recipe': SEVERITY_ATTENTION,
|
|
98
100
|
'gates-declaration': SEVERITY_OPTIONAL,
|
|
99
101
|
'gate-hook': SEVERITY_OPTIONAL,
|
|
102
|
+
'commit-guard': SEVERITY_OPTIONAL,
|
|
100
103
|
'read-lane': SEVERITY_OPTIONAL,
|
|
101
104
|
'read-lane.stale': SEVERITY_ATTENTION,
|
|
102
105
|
'read-lane.missing': SEVERITY_ATTENTION,
|
|
@@ -152,6 +155,7 @@ export const WHATS = Object.freeze({
|
|
|
152
155
|
'review-recipe': '{degraded}',
|
|
153
156
|
'gates-declaration': 'no declared gate matrix (docs/ai/gates.json absent or empty) — gates prompt one by one; the apply PREVIEWS its --apply line, writes nothing',
|
|
154
157
|
'gate-hook': '{n} declared gate(s) prompt per run — the gate-approval hook is not wired',
|
|
158
|
+
'commit-guard': 'the gate matrix is final-run-capable but no commit-guard arms the pre-commit hook — a commit needs no green receipt yet',
|
|
155
159
|
'read-lane': 'the gate hook is wired but the read-only compound lane is off — pipes/chains of seeded reads still prompt one by one',
|
|
156
160
|
'read-lane.stale': 'the read-lane is ON but the placed gate hook is stale — an old hook never reads lanes.json, so the lane is silently dark; reseed it',
|
|
157
161
|
'read-lane.missing': 'the gate hook is wired but its placed file is missing — every Bash call errors and the read-lane is dark; re-place it',
|
|
@@ -205,6 +209,7 @@ export const BENEFITS = Object.freeze({
|
|
|
205
209
|
'review-recipe': 'review coverage — the review recipe you configured actually runs instead of silently degrading',
|
|
206
210
|
'gates-declaration': 'velocity — your project’s gates run as ONE declared batch with a PASS/FAIL table',
|
|
207
211
|
'gate-hook': 'velocity — your own declared gate commands auto-approve byte-exactly (opt-in PreToolUse hook)',
|
|
212
|
+
'commit-guard': 'integrity — commits require the ONE green --final receipt at the exact staged fingerprint (consented pre-commit arm)',
|
|
208
213
|
'read-lane': 'velocity — pipes/chains of your seeded read-only commands auto-approve instead of prompting (opt-in, conservatively classified)',
|
|
209
214
|
'family-freshness': 'currency — placed family members carry the latest shipped fixes and features',
|
|
210
215
|
'sandbox-masks': 'zero clutter — git status shows only your changes (the review domain already ignores the masks by construction)',
|
|
@@ -326,13 +331,13 @@ const probeGates = ({ root, deps, add, skip }) => {
|
|
|
326
331
|
if (sg.error) throw new Error(sg.error);
|
|
327
332
|
if (sg.declarationPresent && sg.declaredGates === null) throw new Error(sg.declarationError ?? 'gates.json present but unreadable');
|
|
328
333
|
// An ABSENT file and the seeded-EMPTY list are equally undeclared (codex R2, Segment B); the
|
|
329
|
-
// apply is the consent-gated
|
|
334
|
+
// apply is the consent-gated gates-init PREVIEW (it proposes entries from the project's own
|
|
330
335
|
// scripts and writes only on an explicit yes) — never the runner.
|
|
331
336
|
if (!sg.declarationPresent || sg.declaredGates === 0) {
|
|
332
|
-
//
|
|
333
|
-
//
|
|
334
|
-
//
|
|
335
|
-
add('gates-declaration', fillTemplate(WHATS['gates-declaration'], {}), `node ${q(toolPath('
|
|
337
|
+
// gates-init writes ONLY with --apply — the apply field stays a PURE executable command
|
|
338
|
+
// (run-exactly-as-rendered feeds it to the shell); the two-step preview semantics live in
|
|
339
|
+
// WHAT, never as prose appended to the command.
|
|
340
|
+
add('gates-declaration', fillTemplate(WHATS['gates-declaration'], {}), `node ${q(toolPath('gates-init.mjs'))} --cwd ${q(root)}`);
|
|
336
341
|
return;
|
|
337
342
|
}
|
|
338
343
|
if (sg.declaredGates > 0 && !sg.wired) {
|
|
@@ -343,6 +348,56 @@ const probeGates = ({ root, deps, add, skip }) => {
|
|
|
343
348
|
}
|
|
344
349
|
};
|
|
345
350
|
|
|
351
|
+
// The D10 consumer surface: once the declaration is FINAL-capable (the canonical core checks
|
|
352
|
+
// present, the checker LAST — the run-gates helper is the one home of that rule), offer the
|
|
353
|
+
// consented commit-guard install — for a MANAGED guardless pre-commit hook AND for an absent one
|
|
354
|
+
// alike; an UNMANAGED hook is a loud skip (manual merge, never an overwrite offer); a
|
|
355
|
+
// non-final-capable declaration gets NO offer (an armed guard over a matrix that cannot mint a
|
|
356
|
+
// receipt would block every commit).
|
|
357
|
+
const INSTALLER_MARKER_RE = /:install-git-hooks\.mjs$/m;
|
|
358
|
+
const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
359
|
+
// Convergence needs the EXACT canonical armed line the installer writes — full-line anchored on
|
|
360
|
+
// the kit's OWN resolved guard path. A commented-out line or a lookalike path (fake-commit-guard)
|
|
361
|
+
// must never read as armed (that would suppress the offer and leave commits unguarded).
|
|
362
|
+
const canonicalGuardLineRe = () => new RegExp(`^node "${escapeRe(toolPath('commit-guard.mjs'))}" --check$`, 'm');
|
|
363
|
+
const probeCommitGuard = ({ root, deps, add, skip }) => {
|
|
364
|
+
try {
|
|
365
|
+
const read = deps.readFile ?? readFileSync;
|
|
366
|
+
const gatesRaw = (() => {
|
|
367
|
+
try {
|
|
368
|
+
return String(read(join(root, 'docs', 'ai', 'gates.json'), 'utf8'));
|
|
369
|
+
} catch {
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
})();
|
|
373
|
+
if (gatesRaw === null) return; // undeclared — the gates-declaration item owns that story
|
|
374
|
+
const gates = JSON.parse(gatesRaw).gates;
|
|
375
|
+
if (!isFinalCapableDeclaration(gates, root)) return; // no receipt to guard yet — no offer
|
|
376
|
+
const installer = join(root, 'scripts', 'install-git-hooks.mjs');
|
|
377
|
+
if (!(deps.exists ?? existsSync)(installer)) {
|
|
378
|
+
skip('commit-guard', new Error('the hook installer (scripts/install-git-hooks.mjs) is not deployed — run upgrade first'));
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
const hooksPath = (deps.gitHooksPath ?? resolveGitHooksPath)(root);
|
|
382
|
+
if (hooksPath === null) return; // not a git tree — nothing to hook
|
|
383
|
+
const hook = (() => {
|
|
384
|
+
try {
|
|
385
|
+
return String(read(join(hooksPath, 'pre-commit'), 'utf8'));
|
|
386
|
+
} catch {
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
})();
|
|
390
|
+
if (hook !== null && canonicalGuardLineRe().test(hook)) return; // armed — converged (managed or not)
|
|
391
|
+
if (hook !== null && !INSTALLER_MARKER_RE.test(hook)) {
|
|
392
|
+
skip('commit-guard', new Error('an UNMANAGED pre-commit hook exists — merge the guard line by hand (the installer refuses to overwrite it)'));
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
add('commit-guard', fillTemplate(WHATS['commit-guard'], {}), `node ${q(installer)} --commit-guard ${q(toolPath('commit-guard.mjs'))}`);
|
|
396
|
+
} catch (err) {
|
|
397
|
+
skip('commit-guard', err);
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
|
|
346
401
|
// The read-lane offer (AD-055 Part II, Help-through-Recommendations): once the gate hook is PLACED
|
|
347
402
|
// and WIRED, offer to enable the opt-in read-only compound lane. Mutually exclusive with the
|
|
348
403
|
// gate-hook item BY CONSTRUCTION — that item keys on `!wired`, this one on `wired` — so an unplaced
|
|
@@ -690,6 +745,7 @@ const PROBES = Object.freeze([
|
|
|
690
745
|
probeSandboxProvision,
|
|
691
746
|
probeReviewRecipe,
|
|
692
747
|
probeGates,
|
|
748
|
+
probeCommitGuard,
|
|
693
749
|
probeReadLane,
|
|
694
750
|
probeFamilyFreshness,
|
|
695
751
|
probeMasksItem,
|
package/tools/release-scan.mjs
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// tripping the (narrow) attribution rules. Only the TEST is self-excluded (it holds attribution
|
|
9
9
|
// fixtures); the scanner source contains no attribution construction, so it is scanned normally.
|
|
10
10
|
//
|
|
11
|
-
// Pure `scanText` for unit fixtures; `scanPaths`/CLI for the release tree. Dependency-free, Node >=
|
|
11
|
+
// Pure `scanText` for unit fixtures; `scanPaths`/CLI for the release tree. Dependency-free, Node >= 22.
|
|
12
12
|
|
|
13
13
|
import { readFileSync, existsSync, readdirSync, statSync } from 'node:fs';
|
|
14
14
|
import { join, extname, basename, resolve } from 'node:path';
|
package/tools/renderers.mjs
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// the JSON mode is handled by the caller (it prints the envelope, not a render). The renderers do
|
|
6
6
|
// LAYOUT + GLYPHS only — phrasing + headline counts already live in the ViewModel.
|
|
7
7
|
//
|
|
8
|
-
// Pure, no side effects, Node >=
|
|
8
|
+
// Pure, no side effects, Node >= 22.
|
|
9
9
|
|
|
10
10
|
import { BLOCK_TITLES, SETTINGS_LABELS, glyphsFor, NO_DEPLOYMENT } from './presentation.mjs';
|
|
11
11
|
|