@nurix/etna 0.19.65 → 0.19.67
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/bin/etna.js +10 -1
- package/data/harness-index.json +1 -5
- package/data/harnesses/code/CLAUDE.md +3 -1
- package/data/harnesses/code/agents/maintenance.md +4 -1
- package/data/harnesses/code/harness.json +7 -2
- package/data/harnesses/code/hooks/check-completion-integrity.mjs +8 -0
- package/data/harnesses/code/hooks/compliance.mjs +139 -27
- package/data/harnesses/code/hooks/lib/frozen.mjs +3 -0
- package/data/harnesses/code/hooks/lib/ledger.mjs +31 -0
- package/data/harnesses/code/hooks/lib/ownership.mjs +3 -1
- package/data/harnesses/code/hooks/lib/session-log.mjs +75 -0
- package/data/harnesses/code/rules/changelog.md +2 -0
- package/data/harnesses/code/skills/documentation/SKILL.md +1 -0
- package/data/harnesses/code/skills/documentation/references/frontmatter.md +1 -1
- package/data/harnesses/code/skills/documentation/scripts/doc-lint.mjs +2 -2
- package/data/harnesses/code/skills/documentation/templates/Readme.md +1 -0
- package/data/harnesses/code/skills/documentation/templates/behavioral-checklist.md +2 -0
- package/data/harnesses/code/skills/documentation/templates/changelog-entry.md +2 -2
- package/data/harnesses/code/skills/documentation/templates/feature.md +1 -0
- package/data/harnesses/code/skills/documentation/templates/implementation-map.md +42 -0
- package/data/harnesses/code/skills/testing/SKILL.md +5 -5
- package/data/harnesses/delivery/agents/maintenance.md +4 -1
- package/data/harnesses/delivery/harness.json +2 -2
- package/data/harnesses/delivery/rules/changelog.md +2 -0
- package/data/harnesses/delivery/skills/documentation/SKILL.md +1 -0
- package/data/harnesses/delivery/skills/documentation/references/frontmatter.md +1 -1
- package/data/harnesses/delivery/skills/documentation/scripts/doc-lint.mjs +2 -2
- package/data/harnesses/delivery/skills/documentation/templates/Readme.md +1 -0
- package/data/harnesses/delivery/skills/documentation/templates/behavioral-checklist.md +2 -0
- package/data/harnesses/delivery/skills/documentation/templates/changelog-entry.md +2 -2
- package/data/harnesses/delivery/skills/documentation/templates/feature.md +1 -0
- package/data/harnesses/delivery/skills/documentation/templates/implementation-map.md +42 -0
- package/data/harnesses/etna/agents/maintenance.md +4 -1
- package/data/harnesses/etna/harness.json +2 -2
- package/data/harnesses/etna/rules/changelog.md +2 -0
- package/data/harnesses/etna/skills/documentation/SKILL.md +1 -0
- package/data/harnesses/etna/skills/documentation/references/frontmatter.md +1 -1
- package/data/harnesses/etna/skills/documentation/scripts/doc-lint.mjs +2 -2
- package/data/harnesses/etna/skills/documentation/templates/Readme.md +1 -0
- package/data/harnesses/etna/skills/documentation/templates/behavioral-checklist.md +2 -0
- package/data/harnesses/etna/skills/documentation/templates/changelog-entry.md +2 -2
- package/data/harnesses/etna/skills/documentation/templates/feature.md +1 -0
- package/data/harnesses/etna/skills/documentation/templates/implementation-map.md +42 -0
- package/lib/install/materialize.mjs +51 -57
- package/package.json +2 -2
- package/data/harnesses/apollo/.mcp.json +0 -8
- package/data/harnesses/apollo/.prettierignore +0 -29
- package/data/harnesses/apollo/.prettierrc.json +0 -3
- package/data/harnesses/apollo/.vscode/settings.json +0 -19
- package/data/harnesses/apollo/CLAUDE.md +0 -162
- package/data/harnesses/apollo/agents/bookkeeper.md +0 -28
- package/data/harnesses/apollo/agents/brainstorm.md +0 -144
- package/data/harnesses/apollo/agents/change-review.md +0 -251
- package/data/harnesses/apollo/agents/lld-mode.md +0 -58
- package/data/harnesses/apollo/agents/maintenance.md +0 -233
- package/data/harnesses/apollo/agents/performance-review.md +0 -223
- package/data/harnesses/apollo/agents/retro.md +0 -46
- package/data/harnesses/apollo/codex/agents/bookkeeper.toml +0 -26
- package/data/harnesses/apollo/codex/agents/brainstorm.toml +0 -142
- package/data/harnesses/apollo/codex/agents/change-review.toml +0 -249
- package/data/harnesses/apollo/codex/agents/lld-mode.toml +0 -52
- package/data/harnesses/apollo/codex/agents/maintenance.toml +0 -228
- package/data/harnesses/apollo/codex/agents/performance-review.toml +0 -222
- package/data/harnesses/apollo/codex/agents/retro.toml +0 -45
- package/data/harnesses/apollo/harness.json +0 -19
- package/data/harnesses/apollo/hooks/approved-packages.md +0 -44
- package/data/harnesses/apollo/hooks/awareness.mjs +0 -405
- package/data/harnesses/apollo/hooks/check-bookkeeping-clean.mjs +0 -19
- package/data/harnesses/apollo/hooks/check-changelog-presence.mjs +0 -57
- package/data/harnesses/apollo/hooks/check-completion-integrity.mjs +0 -71
- package/data/harnesses/apollo/hooks/check-ownership.mjs +0 -22
- package/data/harnesses/apollo/hooks/compliance.mjs +0 -457
- package/data/harnesses/apollo/hooks/hooks.json +0 -23
- package/data/harnesses/apollo/hooks/lib/frozen.mjs +0 -58
- package/data/harnesses/apollo/hooks/lib/ownership.mjs +0 -242
- package/data/harnesses/apollo/hooks/lib/state.mjs +0 -170
- package/data/harnesses/apollo/rules/changelog.md +0 -25
- package/data/harnesses/apollo/rules/database.md +0 -34
- package/data/harnesses/apollo/rules/frontend.md +0 -30
- package/data/harnesses/apollo/rules/general.md +0 -40
- package/data/harnesses/apollo/rules/nomenclature.md +0 -30
- package/data/harnesses/apollo/rules/pnpm.md +0 -57
- package/data/harnesses/apollo/rules/testing.md +0 -64
- package/data/harnesses/apollo/skills/boundary/SKILL.md +0 -75
- package/data/harnesses/apollo/skills/boundary/references/index.md +0 -16
- package/data/harnesses/apollo/skills/boundary/references/mechanisms.md +0 -99
- package/data/harnesses/apollo/skills/boundary/references/packs/async-messaging.md +0 -81
- package/data/harnesses/apollo/skills/boundary/references/packs/auth.md +0 -93
- package/data/harnesses/apollo/skills/boundary/references/packs/build-config-runtime.md +0 -93
- package/data/harnesses/apollo/skills/boundary/references/packs/cloudflare.md +0 -93
- package/data/harnesses/apollo/skills/boundary/references/packs/frontend.md +0 -93
- package/data/harnesses/apollo/skills/boundary/references/packs/generic-tail.md +0 -55
- package/data/harnesses/apollo/skills/boundary/references/packs/persistence.md +0 -93
- package/data/harnesses/apollo/skills/boundary/references/packs/wire-api.md +0 -87
- package/data/harnesses/apollo/skills/brainstorm/README.md +0 -56
- package/data/harnesses/apollo/skills/brainstorm/SKILL.md +0 -173
- package/data/harnesses/apollo/skills/brainstorm/references/doctrine.md +0 -98
- package/data/harnesses/apollo/skills/brainstorm/references/harvest.md +0 -86
- package/data/harnesses/apollo/skills/brainstorm/references/session-schema.md +0 -186
- package/data/harnesses/apollo/skills/brainstorm/scripts/detector.mjs +0 -460
- package/data/harnesses/apollo/skills/design/SKILL.md +0 -121
- package/data/harnesses/apollo/skills/design/components/charts.md +0 -76
- package/data/harnesses/apollo/skills/design/components/collections.md +0 -132
- package/data/harnesses/apollo/skills/design/components/feedback.md +0 -47
- package/data/harnesses/apollo/skills/design/components/hooks.md +0 -35
- package/data/harnesses/apollo/skills/design/components/index.md +0 -143
- package/data/harnesses/apollo/skills/design/components/inputs.md +0 -327
- package/data/harnesses/apollo/skills/design/components/layout.md +0 -52
- package/data/harnesses/apollo/skills/design/components/media.md +0 -66
- package/data/harnesses/apollo/skills/design/components/motion.md +0 -82
- package/data/harnesses/apollo/skills/design/components/navigation.md +0 -29
- package/data/harnesses/apollo/skills/design/components/overlays.md +0 -72
- package/data/harnesses/apollo/skills/design/components/setup.md +0 -40
- package/data/harnesses/apollo/skills/design/components/typography.md +0 -46
- package/data/harnesses/apollo/skills/design/components/utils.md +0 -37
- package/data/harnesses/apollo/skills/design/data/blueprints/browse.md +0 -38
- package/data/harnesses/apollo/skills/design/data/blueprints/creation-flow.md +0 -38
- package/data/harnesses/apollo/skills/design/data/blueprints/dashboard.md +0 -38
- package/data/harnesses/apollo/skills/design/data/blueprints/grid.md +0 -38
- package/data/harnesses/apollo/skills/design/data/blueprints/queue.md +0 -38
- package/data/harnesses/apollo/skills/design/data/blueprints/record-detail.md +0 -38
- package/data/harnesses/apollo/skills/design/data/blueprints/settings.md +0 -38
- package/data/harnesses/apollo/skills/design/data/charts/census.csv +0 -58
- package/data/harnesses/apollo/skills/design/data/checklists/browse.md +0 -21
- package/data/harnesses/apollo/skills/design/data/checklists/creation-flow.md +0 -20
- package/data/harnesses/apollo/skills/design/data/checklists/dashboard.md +0 -21
- package/data/harnesses/apollo/skills/design/data/checklists/grid.md +0 -21
- package/data/harnesses/apollo/skills/design/data/checklists/queue.md +0 -21
- package/data/harnesses/apollo/skills/design/data/checklists/record-detail.md +0 -21
- package/data/harnesses/apollo/skills/design/data/checklists/settings.md +0 -21
- package/data/harnesses/apollo/skills/design/data/chrome/board-column.md +0 -68
- package/data/harnesses/apollo/skills/design/data/chrome/data-table.md +0 -63
- package/data/harnesses/apollo/skills/design/data/chrome/side-panel.md +0 -60
- package/data/harnesses/apollo/skills/design/data/chrome/sidebar.md +0 -78
- package/data/harnesses/apollo/skills/design/data/chrome/stat-cards.md +0 -62
- package/data/harnesses/apollo/skills/design/data/chrome/topbar.md +0 -65
- package/data/harnesses/apollo/skills/design/data/elevation/ant-design-directional-shadow.md +0 -19
- package/data/harnesses/apollo/skills/design/data/elevation/apple-liquid-glass-material.md +0 -18
- package/data/harnesses/apollo/skills/design/data/elevation/atlassian-surface-shadow-pairing.md +0 -20
- package/data/harnesses/apollo/skills/design/data/elevation/fluent-2-key-ambient-ramp.md +0 -20
- package/data/harnesses/apollo/skills/design/data/elevation/github-primer-sparing-shadow.md +0 -19
- package/data/harnesses/apollo/skills/design/data/elevation/ibm-carbon-layer-and-border.md +0 -19
- package/data/harnesses/apollo/skills/design/data/elevation/linear-dark-surface-ladder.md +0 -17
- package/data/harnesses/apollo/skills/design/data/elevation/material-2-key-ambient-dp-ramp.md +0 -19
- package/data/harnesses/apollo/skills/design/data/elevation/material-3-tonal-elevation.md +0 -21
- package/data/harnesses/apollo/skills/design/data/elevation/neobrutalist-hard-offset.md +0 -19
- package/data/harnesses/apollo/skills/design/data/elevation/neumorphic-soft-dual-shadow.md +0 -18
- package/data/harnesses/apollo/skills/design/data/elevation/notion-warm-minimal-hairline.md +0 -17
- package/data/harnesses/apollo/skills/design/data/elevation/polaris-elevation-inset-bevel.md +0 -21
- package/data/harnesses/apollo/skills/design/data/elevation/radix-themes-alpha-ring-scale.md +0 -19
- package/data/harnesses/apollo/skills/design/data/elevation/shadcn-neutral-derived-radius.md +0 -19
- package/data/harnesses/apollo/skills/design/data/elevation/stripe-brand-tinted-dual-shadow.md +0 -18
- package/data/harnesses/apollo/skills/design/data/elevation/tailwind-default-utility-scale.md +0 -19
- package/data/harnesses/apollo/skills/design/data/elevation/vercel-geist-shadow-as-border.md +0 -20
- package/data/harnesses/apollo/skills/design/data/iconography.md +0 -199
- package/data/harnesses/apollo/skills/design/data/motion/accordion-expand.md +0 -18
- package/data/harnesses/apollo/skills/design/data/motion/destructive-hold-confirm.md +0 -18
- package/data/harnesses/apollo/skills/design/data/motion/drag-dismiss-momentum.md +0 -18
- package/data/harnesses/apollo/skills/design/data/motion/empty-state-arrive.md +0 -18
- package/data/harnesses/apollo/skills/design/data/motion/hover-lift.md +0 -18
- package/data/harnesses/apollo/skills/design/data/motion/modal-center-arrive.md +0 -18
- package/data/harnesses/apollo/skills/design/data/motion/number-ticker-roll.md +0 -18
- package/data/harnesses/apollo/skills/design/data/motion/onboarding-bounce.md +0 -18
- package/data/harnesses/apollo/skills/design/data/motion/panel-slide.md +0 -18
- package/data/harnesses/apollo/skills/design/data/motion/press-feedback.md +0 -18
- package/data/harnesses/apollo/skills/design/data/motion/progress-linear-loop.md +0 -18
- package/data/harnesses/apollo/skills/design/data/motion/route-crossfade.md +0 -18
- package/data/harnesses/apollo/skills/design/data/motion/rubber-band-overscroll.md +0 -18
- package/data/harnesses/apollo/skills/design/data/motion/skeleton-shimmer.md +0 -18
- package/data/harnesses/apollo/skills/design/data/motion/stagger-arrive.md +0 -18
- package/data/harnesses/apollo/skills/design/data/motion/toast-enter-stack.md +0 -18
- package/data/harnesses/apollo/skills/design/data/motion/tooltip-chain-instant.md +0 -18
- package/data/harnesses/apollo/skills/design/data/motion/trigger-anchored-reveal.md +0 -18
- package/data/harnesses/apollo/skills/design/data/tells.csv +0 -34
- package/data/harnesses/apollo/skills/design/data/voices/bright-collaborator.md +0 -29
- package/data/harnesses/apollo/skills/design/data/voices/direct-operator.md +0 -29
- package/data/harnesses/apollo/skills/design/data/voices/earnest-helper.md +0 -29
- package/data/harnesses/apollo/skills/design/data/voices/editorial-composed.md +0 -29
- package/data/harnesses/apollo/skills/design/data/voices/quiet-concierge.md +0 -29
- package/data/harnesses/apollo/skills/design/data/voices/warm-guide.md +0 -29
- package/data/harnesses/apollo/skills/design/references/compose.md +0 -23
- package/data/harnesses/apollo/skills/design/references/content.md +0 -28
- package/data/harnesses/apollo/skills/design/references/emission.md +0 -21
- package/data/harnesses/apollo/skills/design/references/existing-apps.md +0 -27
- package/data/harnesses/apollo/skills/design/references/foundations.md +0 -57
- package/data/harnesses/apollo/skills/design/references/intake.md +0 -40
- package/data/harnesses/apollo/skills/design/references/object-pass.md +0 -24
- package/data/harnesses/apollo/skills/design/references/ship-gate.md +0 -29
- package/data/harnesses/apollo/skills/design/references/ux-patterns/Readme.md +0 -195
- package/data/harnesses/apollo/skills/design/references/ux-patterns/ai-layer.md +0 -49
- package/data/harnesses/apollo/skills/design/references/ux-patterns/attributes-lifecycle.md +0 -43
- package/data/harnesses/apollo/skills/design/references/ux-patterns/container-choice.md +0 -44
- package/data/harnesses/apollo/skills/design/references/ux-patterns/creation-paths.md +0 -46
- package/data/harnesses/apollo/skills/design/references/ux-patterns/dashboards.md +0 -42
- package/data/harnesses/apollo/skills/design/references/ux-patterns/modes.md +0 -42
- package/data/harnesses/apollo/skills/design/references/ux-patterns/object-model.md +0 -46
- package/data/harnesses/apollo/skills/design/references/ux-patterns/page-archetype.md +0 -48
- package/data/harnesses/apollo/skills/design/references/ux-patterns/promotion-demotion.md +0 -37
- package/data/harnesses/apollo/skills/design/references/ux-patterns/settings.md +0 -42
- package/data/harnesses/apollo/skills/design/references/ux-patterns/standard-pages.md +0 -46
- package/data/harnesses/apollo/skills/design/references/ux-patterns/state-matrix.md +0 -41
- package/data/harnesses/apollo/skills/design/references/ux-patterns/table-mechanics.md +0 -48
- package/data/harnesses/apollo/skills/design/references/ux-patterns/verbs.md +0 -45
- package/data/harnesses/apollo/skills/design/references/ux-patterns/viewer-context.md +0 -40
- package/data/harnesses/apollo/skills/design/scripts/README-validator.md +0 -27
- package/data/harnesses/apollo/skills/design/scripts/select-chart.mjs +0 -622
- package/data/harnesses/apollo/skills/design/scripts/validate-design-md.mjs +0 -1198
- package/data/harnesses/apollo/skills/documentation/SKILL.md +0 -134
- package/data/harnesses/apollo/skills/documentation/references/examples.md +0 -279
- package/data/harnesses/apollo/skills/documentation/references/frontmatter.md +0 -44
- package/data/harnesses/apollo/skills/documentation/references/instruction-style.md +0 -50
- package/data/harnesses/apollo/skills/documentation/references/style-guide.md +0 -384
- package/data/harnesses/apollo/skills/documentation/references/untemplated.md +0 -38
- package/data/harnesses/apollo/skills/documentation/scripts/doc-lint.mjs +0 -140
- package/data/harnesses/apollo/skills/documentation/templates/Readme.md +0 -34
- package/data/harnesses/apollo/skills/documentation/templates/api-reference.md +0 -20
- package/data/harnesses/apollo/skills/documentation/templates/architecture-decision.md +0 -121
- package/data/harnesses/apollo/skills/documentation/templates/backlog.md +0 -33
- package/data/harnesses/apollo/skills/documentation/templates/behavioral-checklist.md +0 -79
- package/data/harnesses/apollo/skills/documentation/templates/bookkeeping.md +0 -18
- package/data/harnesses/apollo/skills/documentation/templates/changelog-entry.md +0 -57
- package/data/harnesses/apollo/skills/documentation/templates/changelog-index.md +0 -41
- package/data/harnesses/apollo/skills/documentation/templates/docs-readme.md +0 -72
- package/data/harnesses/apollo/skills/documentation/templates/domain-model.md +0 -30
- package/data/harnesses/apollo/skills/documentation/templates/environment-registry.md +0 -15
- package/data/harnesses/apollo/skills/documentation/templates/feature.md +0 -149
- package/data/harnesses/apollo/skills/documentation/templates/glossary.md +0 -11
- package/data/harnesses/apollo/skills/documentation/templates/journal.md +0 -13
- package/data/harnesses/apollo/skills/documentation/templates/memory-main.md +0 -25
- package/data/harnesses/apollo/skills/documentation/templates/migration-runbook.md +0 -34
- package/data/harnesses/apollo/skills/documentation/templates/non-goals.md +0 -11
- package/data/harnesses/apollo/skills/documentation/templates/operations-procedure.md +0 -36
- package/data/harnesses/apollo/skills/documentation/templates/plan.md +0 -88
- package/data/harnesses/apollo/skills/documentation/templates/protected-files.md +0 -37
- package/data/harnesses/apollo/skills/documentation/templates/root-readme.md +0 -104
- package/data/harnesses/apollo/skills/documentation/templates/security-overview.md +0 -27
- package/data/harnesses/apollo/skills/documentation/templates/structure.md +0 -65
- package/data/harnesses/apollo/skills/documentation/templates/system-overview.md +0 -32
- package/data/harnesses/apollo/skills/documentation/templates/testing-strategy.md +0 -29
- package/data/harnesses/apollo/skills/schema-design/SKILL.md +0 -141
- package/data/harnesses/apollo/skills/test/SKILL.md +0 -104
- package/data/harnesses/apollo/skills/testing/SKILL.md +0 -166
- package/data/harnesses/apollo/skills/workflow-mapper/SKILL.md +0 -451
- package/data/harnesses/apollo/skills/workflow-mapper/references/patterns.md +0 -214
- /package/data/harnesses/{apollo → code}/docs/README.md +0 -0
- /package/data/harnesses/{apollo → code}/docs/history/CHANGELOG.md +0 -0
- /package/data/harnesses/{apollo → code}/docs/history/backlog.md +0 -0
- /package/data/harnesses/{apollo → code}/docs/memory/main.md +0 -0
- /package/data/harnesses/{apollo → code}/docs/structure.md +0 -0
- /package/data/harnesses/{apollo → code}/rules/cloudflare.md +0 -0
|
@@ -1,405 +0,0 @@
|
|
|
1
|
-
// harness/code/hooks/awareness.mjs — the inform-never-block hook module.
|
|
2
|
-
// SessionStart: inject the session context block, the ownership index, and a budget-capped drift
|
|
3
|
-
// banner (full report → .claude/state/drift-report.md). PostToolUse (Edit|Write|MultiEdit|Bash):
|
|
4
|
-
// the silent detector — record file→doc touches, append machine-recomputable findings as auto:
|
|
5
|
-
// rows to docs/operations/bookkeeping.md. Never narrates into the session, never blocks, exits 0
|
|
6
|
-
// on every path: everything here is a convenience, absent on any failure.
|
|
7
|
-
|
|
8
|
-
import fs from "node:fs";
|
|
9
|
-
import path from "node:path";
|
|
10
|
-
import { spawnSync } from "node:child_process";
|
|
11
|
-
import {
|
|
12
|
-
readStdin,
|
|
13
|
-
resolveProjectDir,
|
|
14
|
-
buildIndex,
|
|
15
|
-
scanFeatureDocs,
|
|
16
|
-
owningDocs,
|
|
17
|
-
listRepoFiles,
|
|
18
|
-
deadGlobs,
|
|
19
|
-
globToRegExp,
|
|
20
|
-
frontmatter,
|
|
21
|
-
} from "./lib/ownership.mjs";
|
|
22
|
-
import {
|
|
23
|
-
readQueue,
|
|
24
|
-
countRows,
|
|
25
|
-
formatAutoRow,
|
|
26
|
-
formatNeedsSessionRow,
|
|
27
|
-
appendRows,
|
|
28
|
-
resolveRows,
|
|
29
|
-
unexpiredWaivers,
|
|
30
|
-
stateDir,
|
|
31
|
-
recordTouched,
|
|
32
|
-
} from "./lib/state.mjs";
|
|
33
|
-
|
|
34
|
-
const today = () => new Date().toISOString().slice(0, 10);
|
|
35
|
-
|
|
36
|
-
function readSafe(file) {
|
|
37
|
-
try {
|
|
38
|
-
return fs.readFileSync(file, "utf8");
|
|
39
|
-
} catch (e) {
|
|
40
|
-
return "";
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function readJsonSafe(file) {
|
|
45
|
-
try {
|
|
46
|
-
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
47
|
-
} catch (e) {
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// ---------------------------------------------------------------------------
|
|
53
|
-
// SessionStart
|
|
54
|
-
// ---------------------------------------------------------------------------
|
|
55
|
-
|
|
56
|
-
function detectPackageManager(projectDir) {
|
|
57
|
-
const lockfiles = [
|
|
58
|
-
["pnpm-lock.yaml", "pnpm"],
|
|
59
|
-
["package-lock.json", "npm"],
|
|
60
|
-
["yarn.lock", "yarn"],
|
|
61
|
-
["bun.lockb", "bun"],
|
|
62
|
-
["bun.lock", "bun"],
|
|
63
|
-
];
|
|
64
|
-
for (const [file, manager] of lockfiles) {
|
|
65
|
-
if (fs.existsSync(path.join(projectDir, file))) return `${manager} (${file} found)`;
|
|
66
|
-
}
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function harnessLine(projectDir) {
|
|
71
|
-
const marker = readJsonSafe(path.join(projectDir, ".claude", "harness.json"));
|
|
72
|
-
if (!marker || !marker.version) return null;
|
|
73
|
-
const structure = readSafe(path.join(projectDir, "docs", "structure.md"));
|
|
74
|
-
const stamped = (/harness-version:\s*([\w.-]+)/.exec(frontmatter(structure) || structure) || [])[1];
|
|
75
|
-
const state =
|
|
76
|
-
stamped === marker.version
|
|
77
|
-
? "reconciled"
|
|
78
|
-
: stamped
|
|
79
|
-
? `stamped ${stamped} — behind: run maintenance`
|
|
80
|
-
: "unstamped — run maintenance";
|
|
81
|
-
return `Harness: installed ${marker.version} · docs/structure.md ${state}`;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function memoryLine(projectDir) {
|
|
85
|
-
const dir = path.join(projectDir, "docs", "memory");
|
|
86
|
-
let entries;
|
|
87
|
-
try {
|
|
88
|
-
entries = fs.readdirSync(dir).filter((f) => f.endsWith(".md"));
|
|
89
|
-
} catch (e) {
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
return entries.length ? `Memory: ${entries.map((f) => `docs/memory/${f}`).join(", ")}` : null;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function openPlanLines(projectDir) {
|
|
96
|
-
const dir = path.join(projectDir, "docs", "history", "plans");
|
|
97
|
-
let entries;
|
|
98
|
-
try {
|
|
99
|
-
entries = fs.readdirSync(dir).filter((f) => f.endsWith(".md"));
|
|
100
|
-
} catch (e) {
|
|
101
|
-
return [];
|
|
102
|
-
}
|
|
103
|
-
const lines = [];
|
|
104
|
-
for (const file of entries) {
|
|
105
|
-
const text = readSafe(path.join(dir, file));
|
|
106
|
-
const status = (/^\*\*Status:\*\*\s*(.+)$/m.exec(text) || [])[1]?.trim();
|
|
107
|
-
if (!status || /Complete|Abandoned/.test(status)) continue;
|
|
108
|
-
const gates = (/^\*\*Gates:\*\*\s*(.+)$/m.exec(text) || [])[1]?.trim();
|
|
109
|
-
const ledgerRows = (text.match(/^\|\s*\d+\s*\|/gm) || []).length;
|
|
110
|
-
const parts = [`Open plan: ${file} (${status}`];
|
|
111
|
-
if (gates) parts.push(` · Gates: ${gates}`);
|
|
112
|
-
if (ledgerRows) parts.push(` · ledger: ${ledgerRows} rows`);
|
|
113
|
-
lines.push(parts.join("") + ")");
|
|
114
|
-
}
|
|
115
|
-
return lines;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Compute drift facts once; write the full report, return the one-line banner.
|
|
119
|
-
function driftBanner(projectDir) {
|
|
120
|
-
const repoFiles = listRepoFiles(projectDir);
|
|
121
|
-
const dead = deadGlobs(projectDir, repoFiles);
|
|
122
|
-
const queueRows = readQueue(projectDir);
|
|
123
|
-
const counts = countRows(queueRows);
|
|
124
|
-
const waivers = unexpiredWaivers(queueRows, "dead-glob", today());
|
|
125
|
-
const unwaivedDead = dead.filter((d) => !waivers.some((w) => w.body.includes(d.glob)));
|
|
126
|
-
|
|
127
|
-
// A queued dead-glob row covered by an unexpired waiver is resolved history — drop it,
|
|
128
|
-
// so waiving a finding class actually empties the rows it supersedes.
|
|
129
|
-
resolveRows(projectDir, (row) => {
|
|
130
|
-
if (row.kind !== "auto" || !row.body.includes("dead-glob")) return false;
|
|
131
|
-
const m = /`([^`]+)`/.exec(row.body);
|
|
132
|
-
return !!m && waivers.some((w) => w.body.includes(`\`${m[1]}\``));
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
// Docs older than the code they own (doc mtime < newest owned-file mtime).
|
|
136
|
-
const staleDocs = [];
|
|
137
|
-
for (const entry of scanFeatureDocs(projectDir)) {
|
|
138
|
-
let docTime;
|
|
139
|
-
try {
|
|
140
|
-
docTime = fs.statSync(path.join(projectDir, entry.doc)).mtimeMs;
|
|
141
|
-
} catch (e) {
|
|
142
|
-
continue;
|
|
143
|
-
}
|
|
144
|
-
const res = entry.owns.map((g) => globToRegExp(g));
|
|
145
|
-
let newest = 0;
|
|
146
|
-
for (const file of repoFiles) {
|
|
147
|
-
if (!res.some((re) => re.test(file))) continue;
|
|
148
|
-
try {
|
|
149
|
-
newest = Math.max(newest, fs.statSync(path.join(projectDir, file)).mtimeMs);
|
|
150
|
-
} catch (e) {
|
|
151
|
-
/* deleted between walk and stat */
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
if (newest > docTime) staleDocs.push(entry.doc);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const report = [
|
|
158
|
-
`# Drift report — generated ${new Date().toISOString()}`,
|
|
159
|
-
"",
|
|
160
|
-
`## Dead owns: globs (${dead.length}, ${dead.length - unwaivedDead.length} waived)`,
|
|
161
|
-
...dead.map((d) => `- ${d.glob} → ${d.doc}${unwaivedDead.includes(d) ? "" : " (waived)"}`),
|
|
162
|
-
"",
|
|
163
|
-
`## Docs older than owned code (${staleDocs.length})`,
|
|
164
|
-
...staleDocs.map((d) => `- ${d}`),
|
|
165
|
-
"",
|
|
166
|
-
`## Bookkeeping queue (${counts.unresolved} unresolved: ${counts.auto} auto · ${counts.note} note · ${counts.needsSession} needs-session · ${counts.waived} waived)`,
|
|
167
|
-
].join("\n");
|
|
168
|
-
try {
|
|
169
|
-
fs.writeFileSync(path.join(stateDir(projectDir), "drift-report.md"), report + "\n");
|
|
170
|
-
} catch (e) {
|
|
171
|
-
/* report is a convenience */
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (dead.length === 0 && staleDocs.length === 0 && counts.unresolved === 0) return null;
|
|
175
|
-
const waivedNote = dead.length
|
|
176
|
-
? unwaivedDead.length === 0
|
|
177
|
-
? " (all waived)"
|
|
178
|
-
: ` (${unwaivedDead.length} UNWAIVED)`
|
|
179
|
-
: "";
|
|
180
|
-
return (
|
|
181
|
-
`Drift: ${dead.length} dead owns: glob(s)${waivedNote} · ${staleDocs.length} doc(s) older than owned code · ` +
|
|
182
|
-
`bookkeeping: ${counts.unresolved} unresolved row(s) — full report: .claude/state/drift-report.md`
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function sessionStart(projectDir) {
|
|
187
|
-
const lines = [];
|
|
188
|
-
const pm = detectPackageManager(projectDir);
|
|
189
|
-
if (pm) lines.push(`Package manager: ${pm}`);
|
|
190
|
-
const harness = harnessLine(projectDir);
|
|
191
|
-
if (harness) lines.push(harness);
|
|
192
|
-
const memory = memoryLine(projectDir);
|
|
193
|
-
if (memory) lines.push(memory);
|
|
194
|
-
lines.push(...openPlanLines(projectDir).slice(0, 4));
|
|
195
|
-
try {
|
|
196
|
-
const drift = driftBanner(projectDir);
|
|
197
|
-
if (drift) lines.push(drift);
|
|
198
|
-
} catch (e) {
|
|
199
|
-
/* the banner is optional; the index below still injects */
|
|
200
|
-
}
|
|
201
|
-
const block = lines.length
|
|
202
|
-
? "# Session context (generated — a convenience, never a gate)\n" + lines.slice(0, 10).join("\n")
|
|
203
|
-
: "";
|
|
204
|
-
const index = buildIndex(projectDir);
|
|
205
|
-
const context = [block, index].filter(Boolean).join("\n\n");
|
|
206
|
-
if (!context) return;
|
|
207
|
-
process.stdout.write(
|
|
208
|
-
JSON.stringify({
|
|
209
|
-
hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: context },
|
|
210
|
-
})
|
|
211
|
-
);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// ---------------------------------------------------------------------------
|
|
215
|
-
// PostToolUse — the silent detector
|
|
216
|
-
// ---------------------------------------------------------------------------
|
|
217
|
-
|
|
218
|
-
const SOURCE_EXT = /\.(m?[jt]sx?|py|rs|go|rb|java|kt|swift|c|h|cpp|hpp|cs|sh|sql|css|scss|vue|svelte)$/;
|
|
219
|
-
|
|
220
|
-
function relIfInside(projectDir, filePath) {
|
|
221
|
-
if (!filePath) return null;
|
|
222
|
-
const rel = path.relative(projectDir, path.resolve(projectDir, filePath));
|
|
223
|
-
if (rel.startsWith("..") || path.isAbsolute(rel)) return null;
|
|
224
|
-
return rel.split(path.sep).join("/");
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Spawn the installed doc-lint over one file; returns finding lines (empty when absent/clean).
|
|
228
|
-
function docLint(projectDir, rel) {
|
|
229
|
-
const script = path.join(projectDir, ".claude", "skills", "documentation", "scripts", "doc-lint.mjs");
|
|
230
|
-
if (!fs.existsSync(script)) return [];
|
|
231
|
-
const res = spawnSync("node", [script, rel], { cwd: projectDir, encoding: "utf8", timeout: 5000 });
|
|
232
|
-
const out = `${res.stdout || ""}`;
|
|
233
|
-
return out.split("\n").filter((line) => /:\d+ \[(block|warn)\]/.test(line));
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// True when a source file's first lines carry its repo-relative path (the header-comment rule).
|
|
237
|
-
function hasHeaderComment(projectDir, rel) {
|
|
238
|
-
const head = readSafe(path.join(projectDir, rel)).split("\n").slice(0, 5).join("\n");
|
|
239
|
-
return head.includes(rel) || head.includes(path.basename(rel));
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
function detectorForFile(projectDir, rel, meta, rows) {
|
|
243
|
-
const owners = owningDocs(projectDir, rel);
|
|
244
|
-
recordTouched(projectDir, rel, owners.map((o) => o.doc), meta.sessionId);
|
|
245
|
-
|
|
246
|
-
if (rel.endsWith(".md")) {
|
|
247
|
-
// An edited doc resolves any doc-implicated row that pointed at it.
|
|
248
|
-
resolveRows(projectDir, (row) => row.kind === "auto" && row.body.includes("doc-implicated") && row.body.includes(rel));
|
|
249
|
-
|
|
250
|
-
// A plan's Doc Obligations ledger names docs whose stated behavior the plan falsifies. Whether
|
|
251
|
-
// one is discharged is a judgment, never a mechanical fix — so it becomes a `needs-session:`
|
|
252
|
-
// row, the one kind the bookkeeper leaves untouched, rather than an `auto:` row a sub-agent
|
|
253
|
-
// would tick without reading the code. Raising it here rather than walling it at Stop is what
|
|
254
|
-
// gives it the queue's three existing walls (Stop, commit, CI) instead of a fourth special case.
|
|
255
|
-
// Resolve-then-append keeps exactly one row per plan, so re-editing never stacks duplicates.
|
|
256
|
-
if (rel.startsWith("docs/history/plans/")) {
|
|
257
|
-
const planText = readSafe(path.join(projectDir, rel));
|
|
258
|
-
resolveRows(projectDir, (row) => row.kind === "needs-session" && row.body.includes(rel));
|
|
259
|
-
if (
|
|
260
|
-
/^\*\*Status:\*\*\s*Complete\b/m.test(planText) &&
|
|
261
|
-
/^##\s+Doc Obligations\b/m.test(planText) &&
|
|
262
|
-
/^\s*-\s*\[ \]\s*Every Doc Obligations row discharged/m.test(planText)
|
|
263
|
-
) {
|
|
264
|
-
rows.push(
|
|
265
|
-
formatNeedsSessionRow({
|
|
266
|
-
date: meta.date,
|
|
267
|
-
description: `${rel} reached Status: Complete with its Doc Obligations rows undischarged`,
|
|
268
|
-
refs: rel,
|
|
269
|
-
})
|
|
270
|
-
);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
if (rel.startsWith("docs/")) {
|
|
274
|
-
const findings = docLint(projectDir, rel);
|
|
275
|
-
if (findings.length) {
|
|
276
|
-
rows.push(
|
|
277
|
-
formatAutoRow({
|
|
278
|
-
...meta,
|
|
279
|
-
kind: "doc-lint",
|
|
280
|
-
description: `${rel} has ${findings.length} doc-lint finding(s)`,
|
|
281
|
-
action: `fix or doc-lint-disable with reason`,
|
|
282
|
-
})
|
|
283
|
-
);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
const segment = rel.split("/")[1];
|
|
287
|
-
if (rel.startsWith("docs/") && segment && !segment.endsWith(".md")) {
|
|
288
|
-
const structure = readSafe(path.join(projectDir, "docs", "structure.md"));
|
|
289
|
-
if (structure && !structure.includes(`${segment}/`) && !structure.includes(`docs/${segment}`)) {
|
|
290
|
-
rows.push(
|
|
291
|
-
formatAutoRow({
|
|
292
|
-
...meta,
|
|
293
|
-
kind: "doc-placement",
|
|
294
|
-
description: `${rel} sits in a folder absent from docs/structure.md's authority map`,
|
|
295
|
-
action: "move the doc per the add-a-doc table, or record the new folder in structure.md",
|
|
296
|
-
})
|
|
297
|
-
);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
if (rel.startsWith("docs/features/") && !readSafe(path.join(projectDir, "docs", "README.md")).includes(path.basename(rel))) {
|
|
301
|
-
rows.push(
|
|
302
|
-
formatAutoRow({
|
|
303
|
-
...meta,
|
|
304
|
-
kind: "index-row-missing",
|
|
305
|
-
description: `${rel} is not linked from docs/README.md`,
|
|
306
|
-
action: "add its row to the Features index",
|
|
307
|
-
})
|
|
308
|
-
);
|
|
309
|
-
}
|
|
310
|
-
return;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if (SOURCE_EXT.test(rel)) {
|
|
314
|
-
if (!hasHeaderComment(projectDir, rel)) {
|
|
315
|
-
rows.push(
|
|
316
|
-
formatAutoRow({
|
|
317
|
-
...meta,
|
|
318
|
-
kind: "header-missing",
|
|
319
|
-
description: `${rel} lacks its file-path header comment`,
|
|
320
|
-
action: "add the header comment",
|
|
321
|
-
})
|
|
322
|
-
);
|
|
323
|
-
}
|
|
324
|
-
for (const owner of owners) {
|
|
325
|
-
rows.push(
|
|
326
|
-
formatAutoRow({
|
|
327
|
-
...meta,
|
|
328
|
-
kind: "doc-implicated",
|
|
329
|
-
description: `${rel} changed; owning doc ${owner.doc} may need an update (doc-implicated: ${owner.doc})`,
|
|
330
|
-
action: "update the doc or delete this row if no documented behavior changed",
|
|
331
|
-
})
|
|
332
|
-
);
|
|
333
|
-
}
|
|
334
|
-
if (/(schema|init\.sql|migration|auth|webhook|\benv\b|wrangler)/i.test(rel)) {
|
|
335
|
-
rows.push(
|
|
336
|
-
formatAutoRow({
|
|
337
|
-
...meta,
|
|
338
|
-
kind: "boundary-scan-suggested",
|
|
339
|
-
description: `${rel} is contract-heavy`,
|
|
340
|
-
action: "run the boundary skill scoped to the diff",
|
|
341
|
-
})
|
|
342
|
-
);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
function detectRenames(projectDir, command, meta, rows) {
|
|
348
|
-
if (!/\bgit mv\b|\bmv\b/.test(command || "")) return;
|
|
349
|
-
const waivers = unexpiredWaivers(readQueue(projectDir), "dead-glob", today());
|
|
350
|
-
for (const { glob, doc } of deadGlobs(projectDir)) {
|
|
351
|
-
if (waivers.some((w) => w.body.includes(`\`${glob}\``))) continue;
|
|
352
|
-
rows.push(
|
|
353
|
-
formatAutoRow({
|
|
354
|
-
...meta,
|
|
355
|
-
kind: "dead-glob",
|
|
356
|
-
description: `owns: glob \`${glob}\` in ${doc} matches no files`,
|
|
357
|
-
action: `update the doc's owns: frontmatter`,
|
|
358
|
-
})
|
|
359
|
-
);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
function postToolUse(projectDir, payload) {
|
|
364
|
-
const meta = {
|
|
365
|
-
date: today(),
|
|
366
|
-
sessionId: payload.session_id,
|
|
367
|
-
transcriptPath: payload.transcript_path,
|
|
368
|
-
ts: new Date().toISOString(),
|
|
369
|
-
};
|
|
370
|
-
const input = payload.tool_input || {};
|
|
371
|
-
const rows = [];
|
|
372
|
-
if (payload.tool_name === "Bash") {
|
|
373
|
-
detectRenames(projectDir, input.command, meta, rows);
|
|
374
|
-
} else {
|
|
375
|
-
const rel = relIfInside(projectDir, input.file_path || input.notebook_path);
|
|
376
|
-
if (rel) detectorForFile(projectDir, rel, meta, rows);
|
|
377
|
-
// Editing a feature doc's frontmatter can orphan globs the same way a rename can.
|
|
378
|
-
if (rel && rel.startsWith("docs/features/")) detectRenames(projectDir, "git mv", meta, rows);
|
|
379
|
-
}
|
|
380
|
-
appendRows(projectDir, rows);
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
// ---------------------------------------------------------------------------
|
|
384
|
-
|
|
385
|
-
function main() {
|
|
386
|
-
const raw = readStdin();
|
|
387
|
-
let payload = {};
|
|
388
|
-
try {
|
|
389
|
-
payload = raw ? JSON.parse(raw) : {};
|
|
390
|
-
} catch (e) {
|
|
391
|
-
payload = {};
|
|
392
|
-
}
|
|
393
|
-
const projectDir = resolveProjectDir(payload);
|
|
394
|
-
const event = payload.hook_event_name || "SessionStart";
|
|
395
|
-
if (event === "PostToolUse") postToolUse(projectDir, payload);
|
|
396
|
-
else sessionStart(projectDir);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
try {
|
|
400
|
-
main();
|
|
401
|
-
} catch (e) {
|
|
402
|
-
// Inform-only module: on any failure, inject nothing, write nothing, never block.
|
|
403
|
-
} finally {
|
|
404
|
-
process.exit(0);
|
|
405
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
// harness/code/hooks/check-bookkeeping-clean.mjs — CI backstop: no unresolved bookkeeping rows
|
|
2
|
-
// (auto:/note:/needs-session:) may reach the remote. waived: rows pass — they are the recorded,
|
|
3
|
-
// PR-reviewable exceptions. Exit 1 when unresolved rows exist.
|
|
4
|
-
|
|
5
|
-
import { readQueue, countRows } from "./lib/state.mjs";
|
|
6
|
-
|
|
7
|
-
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
8
|
-
const counts = countRows(readQueue(projectDir));
|
|
9
|
-
|
|
10
|
-
if (counts.unresolved === 0) {
|
|
11
|
-
console.log(`check-bookkeeping-clean: OK (${counts.waived} waived row(s))`);
|
|
12
|
-
process.exit(0);
|
|
13
|
-
}
|
|
14
|
-
console.error(
|
|
15
|
-
`check-bookkeeping-clean: ${counts.unresolved} unresolved row(s) in docs/operations/bookkeeping.md ` +
|
|
16
|
-
`(${counts.auto} auto, ${counts.note} note, ${counts.needsSession} needs-session). ` +
|
|
17
|
-
"Drain them (bookkeeper agent), resolve them, or convert to waived: with reason + expires=."
|
|
18
|
-
);
|
|
19
|
-
process.exit(1);
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
// harness/code/hooks/check-changelog-presence.mjs — CI backstop: the commit under test (HEAD)
|
|
2
|
-
// carries a docs/history/changelog/ entry unless it is exempt by the changelog rule's closed list,
|
|
3
|
-
// judged mechanically: lockfile-only, harness-file-only, pure renames, changelog housekeeping,
|
|
4
|
-
// or a bot author. Exit 1 when a non-exempt HEAD stages no entry.
|
|
5
|
-
|
|
6
|
-
import { spawnSync } from "node:child_process";
|
|
7
|
-
import path from "node:path";
|
|
8
|
-
|
|
9
|
-
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
10
|
-
|
|
11
|
-
function git(args) {
|
|
12
|
-
const res = spawnSync("git", args, { cwd: projectDir, encoding: "utf8" });
|
|
13
|
-
return res.status === 0 ? (res.stdout || "").trim() : null;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const nameStatus = git(["diff-tree", "--root", "--no-commit-id", "--name-status", "-r", "HEAD"]) || "";
|
|
17
|
-
const entries = nameStatus
|
|
18
|
-
.split("\n")
|
|
19
|
-
.filter(Boolean)
|
|
20
|
-
.map((line) => {
|
|
21
|
-
const [status, ...files] = line.split("\t");
|
|
22
|
-
return { status, file: files[files.length - 1] };
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
const LOCKFILES = /^(pnpm-lock\.yaml|package-lock\.json|yarn\.lock|bun\.lockb?|Cargo\.lock)$/;
|
|
26
|
-
const HARNESS_PREFIXES = [".claude/", ".agents/", ".codex/"];
|
|
27
|
-
const HARNESS_FILES = [
|
|
28
|
-
"AGENTS.md",
|
|
29
|
-
"CLAUDE.md",
|
|
30
|
-
".mcp.json",
|
|
31
|
-
".vscode/settings.json",
|
|
32
|
-
".prettierignore",
|
|
33
|
-
".prettierrc.json",
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
const exemptFile = (f) =>
|
|
37
|
-
LOCKFILES.test(path.basename(f)) ||
|
|
38
|
-
HARNESS_PREFIXES.some((p) => f.startsWith(p)) ||
|
|
39
|
-
HARNESS_FILES.includes(f) ||
|
|
40
|
-
f.startsWith("docs/history/changelog/") ||
|
|
41
|
-
f === "docs/history/CHANGELOG.md";
|
|
42
|
-
|
|
43
|
-
const author = git(["log", "-1", "--format=%an <%ae>"]) || "";
|
|
44
|
-
const isBot = /\[bot\]|actions@github\.com/i.test(author);
|
|
45
|
-
const allRenames = entries.length > 0 && entries.every((e) => e.status.startsWith("R"));
|
|
46
|
-
const allExempt = entries.length === 0 || entries.every((e) => exemptFile(e.file));
|
|
47
|
-
const hasEntry = entries.some((e) => e.file.startsWith("docs/history/changelog/") && e.file.endsWith(".md"));
|
|
48
|
-
|
|
49
|
-
if (isBot || allRenames || allExempt || hasEntry) {
|
|
50
|
-
console.log("check-changelog-presence: OK");
|
|
51
|
-
process.exit(0);
|
|
52
|
-
}
|
|
53
|
-
console.error(
|
|
54
|
-
"check-changelog-presence: HEAD is a non-exempt commit carrying no docs/history/changelog/ entry. " +
|
|
55
|
-
"Every non-exempt commit ships its entry inside it (exempt: release/bot, harness installs and bumps, lockfile-only, pure renames, changelog housekeeping)."
|
|
56
|
-
);
|
|
57
|
-
process.exit(1);
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
// harness/code/hooks/check-completion-integrity.mjs — CI backstop for plan completion. Scope:
|
|
2
|
-
// plans in the Gates: format only (legacy plans predate the contract and are history). A Complete
|
|
3
|
-
// plan must show a fully discharged surface: no unchecked Done Criteria boxes, the Spec'd LLD
|
|
4
|
-
// flipped active, and the behavioral checklist present. An Abandoned plan must reference the
|
|
5
|
-
// backlog (undischarged obligations outlive the frozen file there). Exit 1 on any violation.
|
|
6
|
-
|
|
7
|
-
import fs from "node:fs";
|
|
8
|
-
import path from "node:path";
|
|
9
|
-
import { frontmatter, field } from "./lib/ownership.mjs";
|
|
10
|
-
|
|
11
|
-
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
12
|
-
const plansDir = path.join(projectDir, "docs", "history", "plans");
|
|
13
|
-
|
|
14
|
-
function readSafe(file) {
|
|
15
|
-
try {
|
|
16
|
-
return fs.readFileSync(file, "utf8");
|
|
17
|
-
} catch (e) {
|
|
18
|
-
return "";
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
let failures = 0;
|
|
23
|
-
const fail = (msg) => {
|
|
24
|
-
failures += 1;
|
|
25
|
-
console.error(`check-completion-integrity: ${msg}`);
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
let plans = [];
|
|
29
|
-
try {
|
|
30
|
-
plans = fs.readdirSync(plansDir).filter((f) => f.endsWith(".md"));
|
|
31
|
-
} catch (e) {
|
|
32
|
-
plans = [];
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
for (const file of plans) {
|
|
36
|
-
const text = readSafe(path.join(plansDir, file));
|
|
37
|
-
if (!/^\*\*Gates:\*\*/m.test(text)) continue; // legacy plan — out of contract
|
|
38
|
-
const status = (/^\*\*Status:\*\*\s*(\S+)/m.exec(text) || [])[1];
|
|
39
|
-
|
|
40
|
-
if (status === "Complete") {
|
|
41
|
-
const unchecked = (text.match(/^- \[ \]/gm) || []).length;
|
|
42
|
-
if (unchecked > 0) fail(`${file} is Complete with ${unchecked} unchecked Done Criteria box(es)`);
|
|
43
|
-
|
|
44
|
-
// EVERY Spec line, not the first. A plan may implement against several feature docs, and a
|
|
45
|
-
// single-match parse silently exempts all but one — the same singular assumption the Doc
|
|
46
|
-
// Obligations ledger exists to break, reintroduced here where nothing would surface it.
|
|
47
|
-
const specs = [...text.matchAll(/^\*\*Spec:\*\*\s*\[[^\]]*\]\(([^)]+)\)/gm)].map((m) => m[1]);
|
|
48
|
-
for (const spec of specs) {
|
|
49
|
-
const specPath = path.resolve(plansDir, spec);
|
|
50
|
-
const specText = readSafe(specPath);
|
|
51
|
-
const specStatus = (field(frontmatter(specText), "status") || "").toString();
|
|
52
|
-
if (specText && specStatus !== "active") {
|
|
53
|
-
fail(`${file} is Complete but its Spec LLD ${spec} is status: ${specStatus || "unknown"} (expected active)`);
|
|
54
|
-
}
|
|
55
|
-
const feature = path.basename(specPath, ".md");
|
|
56
|
-
if (!fs.existsSync(path.join(projectDir, "docs", "tests", `${feature}.md`))) {
|
|
57
|
-
fail(`${file} is Complete but docs/tests/${feature}.md (the behavioral checklist) is missing`);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (status === "Abandoned" && !/backlog/i.test(text)) {
|
|
63
|
-
fail(`${file} is Abandoned without referencing the backlog — undischarged obligations must outlive the frozen plan`);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (failures === 0) {
|
|
68
|
-
console.log("check-completion-integrity: OK");
|
|
69
|
-
process.exit(0);
|
|
70
|
-
}
|
|
71
|
-
process.exit(1);
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
// harness/code/hooks/check-ownership.mjs — CI backstop: every owns: glob must match at least one
|
|
2
|
-
// file, unless an unexpired `waived:` dead-glob row in docs/operations/bookkeeping.md covers it
|
|
3
|
-
// (the waiver names the glob verbatim). Exit 1 on any unwaived dead glob.
|
|
4
|
-
|
|
5
|
-
import { deadGlobs } from "./lib/ownership.mjs";
|
|
6
|
-
import { readQueue, unexpiredWaivers } from "./lib/state.mjs";
|
|
7
|
-
|
|
8
|
-
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
9
|
-
const today = new Date().toISOString().slice(0, 10);
|
|
10
|
-
|
|
11
|
-
const dead = deadGlobs(projectDir);
|
|
12
|
-
const waivers = unexpiredWaivers(readQueue(projectDir), "dead-glob", today);
|
|
13
|
-
const unwaived = dead.filter((d) => !waivers.some((w) => w.body.includes(d.glob)));
|
|
14
|
-
|
|
15
|
-
if (unwaived.length === 0) {
|
|
16
|
-
console.log(`check-ownership: OK (${dead.length} dead glob(s), all waived or none)`);
|
|
17
|
-
process.exit(0);
|
|
18
|
-
}
|
|
19
|
-
console.error(`check-ownership: ${unwaived.length} unwaived dead owns: glob(s):`);
|
|
20
|
-
for (const d of unwaived) console.error(` ${d.glob} → ${d.doc}`);
|
|
21
|
-
console.error("Fix the owning doc's owns: frontmatter, or add a `waived:` dead-glob row with reason + expires=.");
|
|
22
|
-
process.exit(1);
|