simt-dev 0.7.0 → 0.8.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 +191 -870
- package/README.md +188 -213
- package/bin/simt.mjs +19 -55
- package/docs/decisions/ADR-014-workspace-is-configuration.md +2 -2
- package/docs/decisions/ADR-025-a-baseline-is-a-region-in-a-file-the-repo-owns.md +1 -1
- package/docs/decisions/ADR-026-a-config-without-a-region-is-a-file-the-repo-owns.md +1 -1
- package/docs/decisions/ADR-027-the-one-gitattributes-line-that-is-neither-dangerous-nor-empty.md +1 -1
- package/docs/decisions/ADR-028-a-base-file-is-a-floor-so-the-disagreement-it-cannot-repair-becomes-a-check.md +1 -1
- package/docs/decisions/ADR-029-the-pins-a-repository-declares-are-its-own.md +1 -1
- package/docs/decisions/ADR-030-what-a-plan-does-not-ship-is-a-decision-with-a-record.md +1 -1
- package/docs/decisions/ADR-048-a-baseline-carries-only-keys-a-reader-honours.md +1 -1
- package/docs/decisions/ADR-082-the-tracker-override-is-a-manifest-key.md +1 -1
- package/docs/decisions/ADR-099-the-release-verifies-served-bytes-and-the-formula-mirrors-them.md +1 -1
- package/docs/decisions/ADR-117-the-darwin-release-builds-on-supported-macos-15-runners-and-a-re-run-is-idempotent.md +81 -0
- package/docs/decisions/ADR-118-the-bench-corpus-is-keyed-on-history-is-append-only-and-is-portable-as-a-sealed-document.md +91 -0
- package/docs/decisions/ADR-119-the-environment-variable-points-into-a-workspace-the-repo-declared.md +90 -0
- package/docs/decisions/ADR-120-a-pre-push-whose-tree-is-already-proven-green-skips-the-test-gate-on-that-evidence.md +96 -0
- package/docs/decisions/ADR-122-the-shims-move-into-the-state-directory-and-the-legacy-directory-removes-itself.md +94 -0
- package/docs/decisions/ADR-123-a-surplus-positional-is-not-refused-because-arity-is-not-in-the-source.md +57 -0
- package/docs/decisions/ADR-124-the-manifest-has-a-schema-in-the-tool-and-its-unknown-keys-stay-the-repositorys.md +64 -0
- package/docs/decisions/ADR-125-a-shared-context-belongs-to-a-read-and-a-writer-reads-directly.md +65 -0
- package/docs/decisions/ADR-126-the-mcp-surface-stays-a-reader-so-there-is-no-write-path-to-build.md +68 -0
- package/docs/decisions/ADR-127-the-mcp-tool-list-is-written-by-hand-and-roots-is-not-asked-for.md +65 -0
- package/docs/decisions/ADR-128-a-research-plan-is-a-plan-and-the-tracker-reads-it.md +42 -0
- package/ecosystems/bun.json +6 -0
- package/ecosystems/cargo.json +25 -0
- package/ecosystems/composer.json +6 -0
- package/ecosystems/dart.json +24 -0
- package/ecosystems/deno.json +21 -0
- package/ecosystems/devcontainers.json +6 -0
- package/ecosystems/docker-compose.json +6 -0
- package/ecosystems/docker.json +6 -0
- package/ecosystems/dotnet.json +25 -0
- package/ecosystems/elixir.json +23 -0
- package/ecosystems/gitsubmodule.json +6 -0
- package/ecosystems/go.json +25 -0
- package/ecosystems/gradle.json +13 -0
- package/ecosystems/maven.json +23 -0
- package/ecosystems/node.json +25 -0
- package/ecosystems/python.json +20 -0
- package/ecosystems/ruby.json +23 -0
- package/ecosystems/swift.json +25 -0
- package/ecosystems/terraform.json +6 -0
- package/ecosystems/uv.json +6 -0
- package/harnesses/antigravity.json +162 -0
- package/harnesses/claude.json +206 -0
- package/harnesses/codex.json +136 -0
- package/harnesses/copilot.json +88 -0
- package/harnesses/cursor.json +140 -0
- package/harnesses/deepseek.json +99 -0
- package/harnesses/gemini.json +122 -0
- package/harnesses/opencode.json +173 -0
- package/package.json +12 -2
- package/rules/core.md +7 -0
- package/rules/mdc/intake-questions.mdc +8 -0
- package/rules/mdc/slice-commit.mdc +57 -4
- package/src/advice/advice.mjs +3 -2
- package/src/agenthooks/prompt.mjs +5 -2
- package/src/agenthooks/render.mjs +86 -180
- package/src/agenthooks/shim.mjs +1 -1
- package/src/agents/render.mjs +15 -18
- package/src/api.mjs +31 -0
- package/src/argv.mjs +48 -3
- package/src/bench/ablate.mjs +3 -4
- package/src/bench/adapters.json +163 -105
- package/src/bench/adapters.mjs +466 -0
- package/src/bench/arms.mjs +333 -0
- package/src/bench/grade.mjs +33 -73
- package/src/bench/graders.mjs +42 -0
- package/src/bench/mine.mjs +121 -475
- package/src/bench/probe-ledger.mjs +149 -0
- package/src/bench/probes.catalog.mjs +918 -0
- package/src/bench/probes.mjs +79 -1064
- package/src/bench/render.mjs +111 -0
- package/src/bench/report.mjs +120 -962
- package/src/bench/run.mjs +185 -802
- package/src/bench/variants.mjs +4 -5
- package/src/bench/workspace.mjs +160 -0
- package/src/brief/assemble.mjs +48 -15
- package/src/ci/dependabot.mjs +25 -55
- package/src/ci/workflow.mjs +30 -7
- package/src/cli.mjs +45 -0
- package/src/commands/agent.mjs +4 -13
- package/src/commands/agenthook.mjs +70 -40
- package/src/commands/bench.mjs +191 -18
- package/src/commands/brief.mjs +5 -4
- package/src/commands/ci.mjs +13 -14
- package/src/commands/compare.mjs +42 -42
- package/src/commands/completions.mjs +2 -6
- package/src/commands/decide.mjs +4 -4
- package/src/commands/distill.mjs +4 -3
- package/src/commands/docs.mjs +88 -0
- package/src/commands/doctor.mjs +11 -7
- package/src/commands/evidence.mjs +6 -9
- package/src/commands/explain.mjs +0 -1
- package/src/commands/gate.mjs +26 -25
- package/src/commands/graph.mjs +2 -3
- package/src/commands/harness.mjs +6 -5
- package/src/commands/help.mjs +168 -19
- package/src/commands/hooks.mjs +119 -49
- package/src/commands/ignore.mjs +13 -8
- package/src/commands/impact.mjs +8 -15
- package/src/commands/index.mjs +318 -193
- package/src/commands/init-scan.mjs +90 -0
- package/src/commands/init.mjs +124 -105
- package/src/commands/lease.mjs +73 -25
- package/src/commands/lessons.mjs +10 -9
- package/src/commands/local.mjs +3 -3
- package/src/commands/map.mjs +24 -14
- package/src/commands/mcp.mjs +2 -18
- package/src/commands/pack.mjs +9 -6
- package/src/commands/plan.mjs +6 -5
- package/src/commands/plugin.mjs +2 -2
- package/src/commands/prbody.mjs +3 -3
- package/src/commands/repoindex.mjs +12 -11
- package/src/commands/review.mjs +2 -2
- package/src/commands/rules.mjs +37 -16
- package/src/commands/shared.mjs +14 -19
- package/src/commands/skills.mjs +33 -32
- package/src/commands/skillscompile.mjs +7 -8
- package/src/commands/skillsinstall.mjs +7 -6
- package/src/commands/slice.mjs +220 -74
- package/src/commands/spec.mjs +167 -0
- package/src/commands/status.mjs +27 -18
- package/src/commands/sync.mjs +27 -25
- package/src/commands/tests.mjs +18 -21
- package/src/commands/tokens.mjs +21 -26
- package/src/commands/undo.mjs +5 -15
- package/src/commands/upgrade.mjs +15 -7
- package/src/commands/why.mjs +15 -21
- package/src/compare/hooksstate.mjs +2 -6
- package/src/compare/learn.mjs +3 -1
- package/src/compare/row.mjs +0 -2
- package/src/context.mjs +64 -0
- package/src/decisions/index-doc.mjs +46 -0
- package/src/decisions/index.mjs +3 -2
- package/src/diff.mjs +1 -1
- package/src/distill/distill.mjs +14 -1
- package/src/distill/engine.mjs +5 -1
- package/src/docs/commands.mjs +65 -0
- package/src/ecosystem/records.mjs +219 -0
- package/src/explain.mjs +31 -0
- package/src/gates/index.mjs +385 -0
- package/src/graph/compile.mjs +8 -4
- package/src/graph/load.mjs +6 -2
- package/src/graph/spec.mjs +10 -8
- package/src/harness/records.mjs +143 -0
- package/src/hooks/capability.mjs +7 -5
- package/src/hooks/dispatcher.mjs +209 -60
- package/src/hooks/gates.mjs +10 -43
- package/src/hooks/resolve.mjs +4 -14
- package/src/hooks/shim.mjs +5 -2
- package/src/hooks/where.mjs +2 -7
- package/src/identity.mjs +31 -12
- package/src/index/coverage.mjs +58 -0
- package/src/index/extract.mjs +6 -16
- package/src/index/gate.mjs +1 -1
- package/src/index/govern.mjs +26 -9
- package/src/index/impact.mjs +4 -8
- package/src/index/map.mjs +14 -15
- package/src/index/open.mjs +173 -0
- package/src/index/persist.mjs +224 -57
- package/src/index/store.mjs +82 -39
- package/src/index/tests.mjs +2 -6
- package/src/journal.mjs +94 -20
- package/src/kernel/card.mjs +37 -63
- package/src/kernel/checks.mjs +4 -10
- package/src/lease.mjs +102 -7
- package/src/ledger/index.mjs +150 -14
- package/src/local/client.mjs +4 -6
- package/src/locate.mjs +33 -9
- package/src/manifest/schema.mjs +213 -0
- package/src/mcp/paginate.mjs +33 -0
- package/src/mcp/resources.mjs +7 -10
- package/src/mcp/schemas.mjs +512 -0
- package/src/mcp/server.mjs +119 -25
- package/src/mcp/tools-facts.mjs +326 -0
- package/src/mcp/tools-read.mjs +221 -0
- package/src/mcp/tools.mjs +4 -967
- package/src/measure/corpus.mjs +150 -0
- package/src/measure/metrics.mjs +236 -0
- package/src/measure/records.mjs +211 -0
- package/src/measure/session.mjs +3 -2
- package/src/measure/stats.mjs +538 -0
- package/src/measure/task-schema.mjs +125 -0
- package/src/measure/tasks.mjs +69 -0
- package/src/memory/decide.mjs +6 -15
- package/src/memory/lesson-text.mjs +61 -0
- package/src/memory/lessons.mjs +29 -82
- package/src/name.mjs +7 -0
- package/src/node-version.mjs +37 -0
- package/src/output/index.mjs +209 -10
- package/src/packs/manage.mjs +32 -12
- package/src/packs.mjs +20 -2
- package/src/plan/index.mjs +1 -1
- package/src/plugins.mjs +22 -16
- package/src/prbody.mjs +7 -11
- package/src/projection/loaders.mjs +7 -692
- package/src/projection/lock.mjs +11 -1
- package/src/projection/manifest.mjs +29 -468
- package/src/projection/mdc.mjs +7 -8
- package/src/projection/project.mjs +126 -221
- package/src/projection/region.mjs +561 -0
- package/src/projection/rules.mjs +20 -550
- package/src/projection/surfaces.mjs +43 -210
- package/src/projection/targets.mjs +5 -5
- package/src/repo-root.mjs +31 -0
- package/src/resolve/eval.mjs +2 -1
- package/src/resolve/fingerprint.mjs +19 -22
- package/src/resolve/resolve.mjs +1 -41
- package/src/review/critic.mjs +10 -13
- package/src/run.mjs +98 -0
- package/src/secrets.mjs +14 -22
- package/src/state/events.mjs +129 -0
- package/src/status-snapshot.mjs +233 -0
- package/src/status.mjs +2 -6
- package/src/sys/git.mjs +82 -0
- package/src/sys/proc.mjs +151 -0
- package/src/testing/parsers.mjs +265 -0
- package/src/testing/patches.mjs +23 -0
- package/src/testing/paths.mjs +49 -0
- package/src/testing/runners.mjs +186 -0
- package/src/testing/vault.mjs +68 -0
- package/src/text/frontmatter.mjs +49 -0
- package/src/text/tokens.mjs +52 -0
- package/src/toolwords.mjs +3 -0
- package/src/util/fs.mjs +20 -0
- package/src/util/sort.mjs +12 -0
- package/src/workspace.mjs +58 -11
- package/src/write/plan.mjs +82 -0
- package/templates/plan/tracker.json +1 -0
- package/docs/decisions/ADR-033-three-plan-rows-closed-by-decision-not-by-authoring.md +0 -82
- package/docs/decisions/ADR-102-the-two-queued-review-rows-are-closed-by-ruling.md +0 -49
- package/docs/decisions/README.md +0 -600
- package/src/bench/tasks.mjs +0 -36
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Where a repository's hidden test patches live.
|
|
2
|
+
//
|
|
3
|
+
// The vault is outside every checkout on purpose: a container of test patches a
|
|
4
|
+
// bench run is graded against must not be readable from the workspace it is measuring.
|
|
5
|
+
// It is keyed on the repository's root commit, which is identical in every clone and
|
|
6
|
+
// survives remote renames, so the corpus a bench was mined from is found on another
|
|
7
|
+
// machine (review 0003 Q48: an absolute-path key made the committed corpus unrunnable
|
|
8
|
+
// from any other checkout).
|
|
9
|
+
//
|
|
10
|
+
// This lives under `src/testing/` because the map measures recall against the same
|
|
11
|
+
// patches and the index asks whether a path is a test — neither of them runs a benchmark
|
|
12
|
+
// (review 0003 Q79).
|
|
13
|
+
import { execFileSync } from 'node:child_process';
|
|
14
|
+
import { createHash } from 'node:crypto';
|
|
15
|
+
import { homedir, platform as osPlatform } from 'node:os';
|
|
16
|
+
import { basename, join, resolve } from 'node:path';
|
|
17
|
+
|
|
18
|
+
import { CONFIG_DIR } from '../name.mjs';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The stable identity a repository's vault is keyed on: the basename for a
|
|
22
|
+
* human-readable prefix, and the root commit for the digest. Outside git it degrades to
|
|
23
|
+
* the absolute-path digest: a root with no history has no better identity, and the vault
|
|
24
|
+
* path must never fail because git is absent.
|
|
25
|
+
*/
|
|
26
|
+
function repoIdentifier(root) {
|
|
27
|
+
const full = resolve(root);
|
|
28
|
+
const name = basename(full).replace(/[^A-Za-z0-9._-]/g, '-') || 'repo';
|
|
29
|
+
try {
|
|
30
|
+
const sha = execFileSync('git', ['rev-list', '--max-parents=0', 'HEAD'], {
|
|
31
|
+
cwd: root,
|
|
32
|
+
encoding: 'utf8',
|
|
33
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
34
|
+
})
|
|
35
|
+
.trim()
|
|
36
|
+
.split(/\s/)[0];
|
|
37
|
+
if (/^[0-9a-f]{40}$/.test(sha)) return `${name}-root-${sha.slice(0, 8)}`;
|
|
38
|
+
} catch {
|
|
39
|
+
// Not a git root (or no commits): fall through to the path digest.
|
|
40
|
+
}
|
|
41
|
+
const digest = createHash('sha256').update(full).digest('hex').slice(0, 8);
|
|
42
|
+
return `${name}-${digest}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Resolve the hidden verifier vault root outside every worktree.
|
|
47
|
+
*
|
|
48
|
+
* Precedence:
|
|
49
|
+
* 1. `SIMT_DEV_VAULT_DIR` (explicit override for hermetic tests)
|
|
50
|
+
* 2. `$XDG_CACHE_HOME/simt-dev/vault/<repo-id>`
|
|
51
|
+
* 3. `~/Library/Caches/simt-dev/vault/<repo-id>` on macOS, or `~/.cache/simt-dev/vault/<repo-id>`
|
|
52
|
+
*
|
|
53
|
+
* @param {string} root
|
|
54
|
+
* @param {{env?: NodeJS.ProcessEnv, home?: string, platform?: string}} [options]
|
|
55
|
+
* @returns {string}
|
|
56
|
+
*/
|
|
57
|
+
export function resolveVaultDir(root, { env = process.env, home = homedir(), platform = osPlatform() } = {}) {
|
|
58
|
+
const repoId = repoIdentifier(root);
|
|
59
|
+
if (env.SIMT_DEV_VAULT_DIR) {
|
|
60
|
+
return join(resolve(env.SIMT_DEV_VAULT_DIR), repoId);
|
|
61
|
+
}
|
|
62
|
+
const cacheBase = env.XDG_CACHE_HOME
|
|
63
|
+
? resolve(env.XDG_CACHE_HOME)
|
|
64
|
+
: platform === 'darwin'
|
|
65
|
+
? join(home, 'Library', 'Caches')
|
|
66
|
+
: join(home, '.cache');
|
|
67
|
+
return join(cacheBase, CONFIG_DIR, 'vault', repoId);
|
|
68
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// The one frontmatter block: an opening `---` line, a body, a closing `---` line,
|
|
2
|
+
// at the top of a document.
|
|
3
|
+
//
|
|
4
|
+
// Eleven call sites carried their own copy of the regex, and the copies disagreed
|
|
5
|
+
// about CRLF: two tolerated `\r\n`, nine assumed `\n`, so a document checked out
|
|
6
|
+
// with CRLF line endings parsed in one command and came back with the block glued
|
|
7
|
+
// to the body in another. The shape here is the superset: it tolerates CRLF, and it
|
|
8
|
+
// fixes what "the block" is, so a caller that asks for the block, the body or the
|
|
9
|
+
// stripped document gets all three from one match.
|
|
10
|
+
|
|
11
|
+
/** The block as written: the two `---` lines and what is between them. The line
|
|
12
|
+
* ending after the closing `---` is NOT part of the block, because a caller that
|
|
13
|
+
* swaps the block in place must leave that newline where the file had it. */
|
|
14
|
+
const BLOCK = /^---\r?\n([\s\S]*?)\r?\n---/;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Split a leading frontmatter block from its document.
|
|
18
|
+
*
|
|
19
|
+
* @param {string} text
|
|
20
|
+
* @returns {{front: string|null, body: string, block: string|null}}
|
|
21
|
+
* `front` is the interior between the `---` lines, `block` is the block as written,
|
|
22
|
+
* and `body` is the document after the block and the one line ending that follows
|
|
23
|
+
* it. All three are null-or-unchanged when the document carries no block.
|
|
24
|
+
*/
|
|
25
|
+
export function parseFrontmatter(text) {
|
|
26
|
+
const raw = String(text ?? '');
|
|
27
|
+
const m = BLOCK.exec(raw);
|
|
28
|
+
if (!m) return { front: null, body: raw, block: null };
|
|
29
|
+
const rest = raw.slice(m[0].length);
|
|
30
|
+
const body = rest.startsWith('\r\n') ? rest.slice(2) : rest.startsWith('\n') ? rest.slice(1) : rest;
|
|
31
|
+
return { front: m[1], body, block: m[0] };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The block as written, or null when the document carries none. */
|
|
35
|
+
export function frontmatterBlock(text) {
|
|
36
|
+
const m = BLOCK.exec(String(text ?? ''));
|
|
37
|
+
return m ? m[0] : null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The document without its block, and without the line endings that followed it.
|
|
42
|
+
*
|
|
43
|
+
* The trailing-newline swallow lives here rather than in BLOCK on purpose: a strip
|
|
44
|
+
* that left the blank lines in place would send a following `^`-anchored strip (the
|
|
45
|
+
* H1 a region renderer drops) at a bare newline, and it would silently match nothing.
|
|
46
|
+
*/
|
|
47
|
+
export function stripFrontmatter(text) {
|
|
48
|
+
return String(text ?? '').replace(/^---\r?\n[\s\S]*?\r?\n---(?:\r?\n)*/, '');
|
|
49
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// The token split and the two stopword lists, in one place.
|
|
2
|
+
//
|
|
3
|
+
// The two lists are deliberately NOT merged. The resolver's list is the English
|
|
4
|
+
// filler a task string carries ("fix the thing"), hand-authored because a resolver
|
|
5
|
+
// nobody can review is not a resolver; the index's list is the vocabulary a task
|
|
6
|
+
// about code carries ("file", "test", "line"), sized for a different signal. Merging
|
|
7
|
+
// them would change what each one ranks, and a ranking change is a behaviour change
|
|
8
|
+
// this deduplication was not asked to make.
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Split a task string into comparable tokens.
|
|
12
|
+
*
|
|
13
|
+
* Length 2 is kept. The floor used to be 3, and it dropped the most specific words in
|
|
14
|
+
* this domain: `d1`, `r2`, `kv`, `ci` are two-letter vocabulary, not noise, and a task
|
|
15
|
+
* saying "fix d1 migration" lost the one word that identified the change. The cost is
|
|
16
|
+
* that six stopwords of two letters (`in on of or an so`) now reach the summary signal,
|
|
17
|
+
* which is S04's finding and not something this change can resolve on its own - the
|
|
18
|
+
* remedy there is a stopword list, not a longer minimum.
|
|
19
|
+
*/
|
|
20
|
+
export function tokenize(text) {
|
|
21
|
+
return String(text ?? '')
|
|
22
|
+
.toLowerCase()
|
|
23
|
+
.split(/[^a-z0-9]+/)
|
|
24
|
+
.filter((t) => t.length > 1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The English filler a task string carries. Hand-authored rather than derived, for the
|
|
29
|
+
* same reason the capability aliases are: a resolver nobody can review is not a
|
|
30
|
+
* resolver.
|
|
31
|
+
*/
|
|
32
|
+
export const STOPWORDS = new Set([
|
|
33
|
+
'about', 'after', 'all', 'also', 'and', 'an', 'any', 'are', 'as', 'at', 'be', 'because',
|
|
34
|
+
'been', 'before', 'being', 'both', 'but', 'by', 'can', 'could', 'did', 'do', 'does', 'each',
|
|
35
|
+
'for', 'from', 'had', 'has', 'have', 'he', 'her', 'here', 'his', 'how', 'if', 'in', 'into',
|
|
36
|
+
'is', 'it', 'its', 'just', 'like', 'make', 'many', 'may', 'me', 'more', 'most', 'must', 'my',
|
|
37
|
+
'no', 'not', 'now', 'of', 'off', 'on', 'one', 'only', 'onto', 'or', 'other', 'our', 'out',
|
|
38
|
+
'over', 'own', 'per', 'same', 'see', 'set', 'she', 'should', 'so', 'some', 'such', 'than',
|
|
39
|
+
'that', 'the', 'their', 'them', 'then', 'there', 'these', 'they', 'this', 'those', 'to', 'too',
|
|
40
|
+
'two', 'under', 'up', 'us', 'use', 'used', 'using', 'very', 'was', 'way', 'we', 'were', 'what',
|
|
41
|
+
'when', 'where', 'which', 'while', 'who', 'why', 'will', 'with', 'within', 'without', 'would',
|
|
42
|
+
'you', 'your',
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
/** The code vocabulary a task about a tree carries, for the index's term signal. */
|
|
46
|
+
export const INDEX_STOP = new Set([
|
|
47
|
+
'this', 'that', 'with', 'from', 'have', 'been', 'into', 'only', 'when', 'what',
|
|
48
|
+
'which', 'their', 'about', 'after', 'before', 'where', 'there', 'these', 'those',
|
|
49
|
+
'would', 'could', 'should', 'every', 'other', 'than', 'then', 'them', 'they',
|
|
50
|
+
'your', 'will', 'does', 'file', 'files', 'test', 'tests', 'line', 'lines', 'code',
|
|
51
|
+
'also', 'just', 'more', 'some', 'such', 'each', 'same', 'over', 'used', 'using',
|
|
52
|
+
]);
|
package/src/toolwords.mjs
CHANGED
|
@@ -23,6 +23,7 @@ import { LESSON_KEYS } from './memory/lessons.mjs';
|
|
|
23
23
|
import { DECISION_KEYS } from './memory/decide.mjs';
|
|
24
24
|
import { PROMPT_WORDS } from './mcp/prompts.mjs';
|
|
25
25
|
import { AGENT_STEM, SKILL_STEM } from './agents/render.mjs';
|
|
26
|
+
import { CORPUS_KIND } from './measure/corpus.mjs';
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
29
|
* `simt-` names that belong to this tool and never to a repository.
|
|
@@ -50,6 +51,8 @@ export const TOOL_WORDS = new Set([
|
|
|
50
51
|
AGENT_STEM,
|
|
51
52
|
// The companion skill the agent reads on demand; its stem is the directory name.
|
|
52
53
|
SKILL_STEM,
|
|
54
|
+
// The kind stamp this tool writes into every sealed bench corpus document it exports.
|
|
55
|
+
CORPUS_KIND,
|
|
53
56
|
]);
|
|
54
57
|
|
|
55
58
|
/**
|
package/src/util/fs.mjs
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Small file and output helpers with two or more call sites, too small to earn
|
|
2
|
+
// a module apiece.
|
|
3
|
+
|
|
4
|
+
import { readFile } from 'node:fs/promises';
|
|
5
|
+
|
|
6
|
+
/** Read a file as UTF-8, answering null when it is not there. */
|
|
7
|
+
export async function readIfExists(path) {
|
|
8
|
+
try {
|
|
9
|
+
return await readFile(path, 'utf8');
|
|
10
|
+
} catch {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** `1234567`, never `1,234,567`: two machines must print the same bytes (ADR-013).
|
|
16
|
+
* `toLocaleString` output is locale-dependent, and these commands exist so that two
|
|
17
|
+
* machines produce two numbers a pull request can compare. */
|
|
18
|
+
export function plainNumber(n) {
|
|
19
|
+
return String(n);
|
|
20
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// The one string comparison for every sort that orders paths, names and ids.
|
|
2
|
+
//
|
|
3
|
+
// Seven copies of the same three-line comparator grew up under src/, spelled two
|
|
4
|
+
// different ways, and a divergence in any one of them would quietly reorder a list
|
|
5
|
+
// a reader has memorized. One definition, one order.
|
|
6
|
+
|
|
7
|
+
/** Deterministic string order: -1, 0 or 1. */
|
|
8
|
+
export function cmp(a, b) {
|
|
9
|
+
if (a < b) return -1;
|
|
10
|
+
if (a > b) return 1;
|
|
11
|
+
return 0;
|
|
12
|
+
}
|
package/src/workspace.mjs
CHANGED
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
// confidently wrong.
|
|
22
22
|
|
|
23
23
|
import { readFile } from 'node:fs/promises';
|
|
24
|
+
|
|
25
|
+
import { namedRoot } from './repo-root.mjs';
|
|
24
26
|
import { existsSync } from 'node:fs';
|
|
25
27
|
import { homedir, platform as osPlatform } from 'node:os';
|
|
26
28
|
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
@@ -38,7 +40,8 @@ export const PACKAGE_ROOT = resolve(HERE, '..');
|
|
|
38
40
|
// other LOCKED on-disk names, and a second copy here is a name that can drift from the
|
|
39
41
|
// list of names that are allowed to move. Imported as well as re-exported, because a
|
|
40
42
|
// bare `export { X } from` does not bind X here and every use below would fail.
|
|
41
|
-
import { CONFIG_DIR } from './name.mjs';
|
|
43
|
+
import { CONFIG_DIR, MANIFEST_FILE } from './name.mjs';
|
|
44
|
+
import { manifestPath, readManifest } from './projection/manifest.mjs';
|
|
42
45
|
export { CONFIG_DIR };
|
|
43
46
|
export const CONFIG_FILE = 'config.json';
|
|
44
47
|
|
|
@@ -133,14 +136,25 @@ async function looksLikeWorkspace(dir) {
|
|
|
133
136
|
/**
|
|
134
137
|
* Resolve the workspace.
|
|
135
138
|
*
|
|
136
|
-
* Precedence, highest first: `--workspace`,
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
+
* Precedence, highest first (ADR-119): `--workspace`, the repository's own manifest
|
|
140
|
+
* declaration, `SIMT_DEV_WORKSPACE` - a value that names a directory is honoured only
|
|
141
|
+
* where the manifest declares a workspace, because the variable is a pointer INTO a
|
|
142
|
+
* workspace the repo opted into, not one imposed on a repo that never declared one;
|
|
143
|
+
* a value that declines is honoured from anywhere, because a decline points at
|
|
144
|
+
* nothing - the manifest's value, the user config file, then the sibling heuristic
|
|
145
|
+
* of this package, which is accepted only if it actually contains a git repository.
|
|
146
|
+
*
|
|
147
|
+
* A relative manifest value resolves against the manifest's directory, not the
|
|
148
|
+
* process's, so `workspace: ".."` means the same thing from any subdirectory.
|
|
139
149
|
*
|
|
150
|
+
* @param {{repoRoot?: string}} [options] `repoRoot` names the repository whose manifest
|
|
151
|
+
* declares the workspace; it defaults to `process.cwd()`, which is the repository
|
|
152
|
+
* for the way every command is run.
|
|
140
153
|
* @returns {Promise<{root: string|null, source: string, detail: string, searched: string[], error: string|null}>}
|
|
141
154
|
*/
|
|
142
155
|
export async function resolveWorkspace(flags = {}, options = {}) {
|
|
143
156
|
const env = options.env ?? process.env;
|
|
157
|
+
const repoRoot = options.repoRoot ?? process.cwd();
|
|
144
158
|
const searched = configPaths(options);
|
|
145
159
|
|
|
146
160
|
// The config file is read FIRST, before precedence is applied, and a parse failure
|
|
@@ -169,16 +183,49 @@ export async function resolveWorkspace(flags = {}, options = {}) {
|
|
|
169
183
|
);
|
|
170
184
|
}
|
|
171
185
|
|
|
186
|
+
// The repository's own declaration (ADR-119). It is read before the environment
|
|
187
|
+
// variable because it is what the variable is allowed to point into: a repo that
|
|
188
|
+
// declared no workspace is a single repo with no workspace, and an environment
|
|
189
|
+
// variable cannot turn it into a member of a workspace nobody reviewed.
|
|
190
|
+
const manifest = await readManifest(repoRoot);
|
|
191
|
+
if (manifest.error) {
|
|
192
|
+
return {
|
|
193
|
+
root: null,
|
|
194
|
+
source: 'repo',
|
|
195
|
+
detail: await manifestPath(repoRoot),
|
|
196
|
+
searched,
|
|
197
|
+
error: manifest.error,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
let repoValue = null;
|
|
201
|
+
if (manifest.exists && typeof manifest.manifest.workspace === 'string' && manifest.manifest.workspace.trim() !== '') {
|
|
202
|
+
const off = disabled(manifest.manifest.workspace, 'repo', `${MANIFEST_FILE} workspace`, searched);
|
|
203
|
+
if (off) return off; // the repo declined; the environment variable cannot enforce one
|
|
204
|
+
repoValue = manifest.manifest.workspace;
|
|
205
|
+
}
|
|
206
|
+
|
|
172
207
|
if (env.SIMT_DEV_WORKSPACE) {
|
|
173
|
-
|
|
174
|
-
|
|
208
|
+
const off = disabled(env.SIMT_DEV_WORKSPACE, 'env', 'SIMT_DEV_WORKSPACE', searched);
|
|
209
|
+
if (off) return off; // a decline points at nothing, so it needs no declaration to work
|
|
210
|
+
if (repoValue !== null) {
|
|
211
|
+
return {
|
|
175
212
|
root: absolute(env.SIMT_DEV_WORKSPACE),
|
|
176
213
|
source: 'env',
|
|
177
214
|
detail: 'SIMT_DEV_WORKSPACE',
|
|
178
215
|
searched,
|
|
179
216
|
error: null,
|
|
180
|
-
}
|
|
181
|
-
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (repoValue !== null) {
|
|
222
|
+
return {
|
|
223
|
+
root: absolute(repoValue, repoRoot),
|
|
224
|
+
source: 'repo',
|
|
225
|
+
detail: `${MANIFEST_FILE} workspace`,
|
|
226
|
+
searched,
|
|
227
|
+
error: null,
|
|
228
|
+
};
|
|
182
229
|
}
|
|
183
230
|
|
|
184
231
|
if (user.exists && typeof user.config.workspace === 'string' && user.config.workspace.length > 0) {
|
|
@@ -245,11 +292,11 @@ export const WORKSPACE_REMEDY = (() => {
|
|
|
245
292
|
return `--workspace <path>, the SIMT_DEV_WORKSPACE environment variable, or ${where}`;
|
|
246
293
|
})();
|
|
247
294
|
|
|
248
|
-
function absolute(p) {
|
|
295
|
+
function absolute(p, base = process.cwd()) {
|
|
249
296
|
const expanded = p.startsWith('~/')
|
|
250
297
|
? join(homedir(), p.slice(2))
|
|
251
298
|
: p;
|
|
252
|
-
return isAbsolute(expanded) ? expanded : resolve(expanded);
|
|
299
|
+
return isAbsolute(expanded) ? expanded : resolve(base, expanded);
|
|
253
300
|
}
|
|
254
301
|
|
|
255
302
|
/**
|
|
@@ -266,7 +313,7 @@ function absolute(p) {
|
|
|
266
313
|
* answers it from the filesystem rather than from a guess.
|
|
267
314
|
*/
|
|
268
315
|
export async function targetRoot(flags = {}, positional = []) {
|
|
269
|
-
const explicit = flags
|
|
316
|
+
const explicit = namedRoot(flags, positional);
|
|
270
317
|
if (explicit) {
|
|
271
318
|
return { root: absolute(explicit), source: 'flag', detail: explicit, ws: null };
|
|
272
319
|
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// One change record, and the one function that applies it (review 0003 Q68).
|
|
2
|
+
//
|
|
3
|
+
// Fourteen writers hand-roll the same five steps: read the before bytes, decide create or
|
|
4
|
+
// update, write through the journal, commit the set, print the diff. They do not all do
|
|
5
|
+
// them in the same order, and the differences are defects rather than decisions - a
|
|
6
|
+
// writer that skipped the journal (the tracker, the lock, `skills compile`) could not be
|
|
7
|
+
// undone, and one that read the before bytes after writing produced a diff against
|
|
8
|
+
// itself.
|
|
9
|
+
//
|
|
10
|
+
// A change is data: what the path will hold, what it holds now, who owns it and why it
|
|
11
|
+
// changes. `applyPlan` is the only place that knows about journals, directories and
|
|
12
|
+
// `--yes`, so a writer states the change and nothing else.
|
|
13
|
+
//
|
|
14
|
+
// The record carries no `action` from the caller. Create, update, remove and unchanged
|
|
15
|
+
// are derived from `before` and `after`, because a writer that passes an action it
|
|
16
|
+
// computed by hand is the bug this replaces.
|
|
17
|
+
import { isAbsolute, join } from 'node:path';
|
|
18
|
+
|
|
19
|
+
import { commitJournal, journaledRemoveFile, journaledWriteFile } from '../journal.mjs';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* One change to one path.
|
|
23
|
+
*
|
|
24
|
+
* `kind: 'remove'` is a deletion, and `after` is then null. `owner` and `reason` are for
|
|
25
|
+
* the report a person reads: which writer owns the file and why it is changing. `mode` is
|
|
26
|
+
* permission bits for a file that must be executable, and rides on the same entry so
|
|
27
|
+
* `undo` restores the mode it found.
|
|
28
|
+
*/
|
|
29
|
+
export function change({ path, kind = 'file', before, after, owner, reason, mode }) {
|
|
30
|
+
if (kind !== 'file' && kind !== 'remove') throw new Error(`unknown change kind: ${kind}`);
|
|
31
|
+
return {
|
|
32
|
+
path,
|
|
33
|
+
kind,
|
|
34
|
+
before,
|
|
35
|
+
after,
|
|
36
|
+
owner: owner ?? null,
|
|
37
|
+
reason: reason ?? null,
|
|
38
|
+
mode,
|
|
39
|
+
action: actionOf(kind, before, after),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function actionOf(kind, before, after) {
|
|
44
|
+
if (kind === 'remove') return before === null ? 'unchanged' : 'remove';
|
|
45
|
+
if (before === after) return 'unchanged';
|
|
46
|
+
if (before === null) return 'create';
|
|
47
|
+
return 'update';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** The changes that would do something, in the order the caller listed them. */
|
|
51
|
+
export function pending(changes) {
|
|
52
|
+
return (changes ?? []).filter((c) => c.action !== 'unchanged');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Apply the plan, or report it.
|
|
57
|
+
*
|
|
58
|
+
* Without `mode.apply` nothing touches the disk and the pending paths come back as
|
|
59
|
+
* `pending`: a rehearsal and a real run answer the same question with the same plan, and
|
|
60
|
+
* the only difference is whether the bytes moved. With it, every change is journaled into
|
|
61
|
+
* one set under `label`, so `undo` restores the run rather than one file of it.
|
|
62
|
+
*
|
|
63
|
+
* A writer whose run is more than one plan - `init` writes a manifest, an identity and a
|
|
64
|
+
* set of targets and commits once - passes its own `journal` and `commit: false`. The
|
|
65
|
+
* entries land in the caller's set, so the run stays one undoable step rather than one
|
|
66
|
+
* step per plan.
|
|
67
|
+
*/
|
|
68
|
+
export async function applyPlan(root, changes, { mode, label, journal: shared, commit = true } = {}) {
|
|
69
|
+
const todo = pending(changes);
|
|
70
|
+
if (todo.length === 0) return { applied: false, written: [], pending: [], journal: [] };
|
|
71
|
+
if (!mode?.apply) return { applied: false, written: [], pending: todo.map((c) => c.path), journal: [] };
|
|
72
|
+
|
|
73
|
+
const journal = shared ?? [];
|
|
74
|
+
const first = journal.length;
|
|
75
|
+
for (const c of todo) {
|
|
76
|
+
const path = isAbsolute(c.path) ? c.path : join(root, c.path);
|
|
77
|
+
if (c.kind === 'remove') await journaledRemoveFile(root, journal, path);
|
|
78
|
+
else await journaledWriteFile(root, journal, path, c.after, c.mode === undefined ? {} : { mode: c.mode });
|
|
79
|
+
}
|
|
80
|
+
if (commit && journal.length > first) await commitJournal(root, label, journal);
|
|
81
|
+
return { applied: true, written: todo.map((c) => c.path), pending: [], journal };
|
|
82
|
+
}
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
# ADR-033 — three plan rows closed by decision, not by authoring
|
|
2
|
-
|
|
3
|
-
Status: accepted · 2026-10-08
|
|
4
|
-
**Affects:** `docs/plans/0001-centralise-agent-rules.md` §3.1 row 3, §6 phase 6, §6 phase 7.
|
|
5
|
-
Ships no rule text. **Refuses:** the seven agent policies row 3 asks for, the twin collapse
|
|
6
|
-
phase 6 asks for, and untracking the two committed `local.properties` files phase 7 does not
|
|
7
|
-
mention but whose existence made the sweep look unfinished.
|
|
8
|
-
|
|
9
|
-
## Context
|
|
10
|
-
|
|
11
|
-
Closing plan 0001 meant auditing each row against the tree rather than against the plan's own
|
|
12
|
-
prose, because the plan was written on 2026-10-05 and three things changed underneath it. This
|
|
13
|
-
record is the audit's residue: three items that read as unfinished work and are not.
|
|
14
|
-
|
|
15
|
-
Row 3 asked for eight agent policies as separate files. Seven were never authored. A search for
|
|
16
|
-
their source material across every tracked `*.md` and `*.mdc` in the estate returns two files,
|
|
17
|
-
neither of which is a policy: they are a progress ledger and a completed UX plan that happen to
|
|
18
|
-
contain the phrases. The three most specific of the eight — the 70/30 quality-versus-performance
|
|
19
|
-
split, the 12-state coverage list, the matrix-trigger heuristic — have no source anywhere. Writing
|
|
20
|
-
them would mean inventing doctrine this estate never agreed to, into `rules/core.md`, from where
|
|
21
|
-
every adopted repo would inherit it.
|
|
22
|
-
|
|
23
|
-
Phase 6 asked to "collapse the 7 drifted Social skill twins to canonical + stub". The registry's
|
|
24
|
-
`sources.role` mechanism landed the same evening, two hours after the plan was authored, and
|
|
25
|
-
answers the row directly. All three resolvers select `role === 'canonical'` and nothing else —
|
|
26
|
-
`src/commands/skills.mjs` twice, `src/commands/skillsinstall.mjs:291`, `src/mcp/tools.mjs:192` — so
|
|
27
|
-
a copy declared `duplicate` or `variant` is never installed. The one pair that genuinely had to be
|
|
28
|
-
collapsed, `add-country-galaxy`, is already a stub in `repo-14` pointing at the API repo.
|
|
29
|
-
|
|
30
|
-
Phase 7 asked for a sweep of hardcoded absolute paths. The sweep found 17 tracked files. Six were
|
|
31
|
-
fixed. The two Android `local.properties` files were not, and were nearly fixed by reflex.
|
|
32
|
-
|
|
33
|
-
## Rulings
|
|
34
|
-
|
|
35
|
-
1. **A row is closed by ruling on it when the thing it asks for was never a decision anyone made.**
|
|
36
|
-
Row 3's eight policies are closed: the four claims `rules/core.md` already carries — the
|
|
37
|
-
minimum viable team, never weakening a security control, anti-overclaim, and a stated agent
|
|
38
|
-
budget that never converts a failure into an approval — are the doctrine, and they ship. The
|
|
39
|
-
other four, including the 70/30 split, the 12-state list and the matrix-trigger heuristic, are
|
|
40
|
-
recorded here as unmade. If one is wanted it needs someone who held the original
|
|
41
|
-
conversation, not someone reconstructing it from a table cell.
|
|
42
|
-
|
|
43
|
-
2. **A mechanism that supersedes a row retires the row, and the deletion is the wrong move.** The
|
|
44
|
-
`sources.role` vocabulary already distinguishes `canonical`, `duplicate`, `variant`, `stub` and
|
|
45
|
-
`deprecated`, and `graph lint` passes with all of them in use. Deleting a `duplicate` copy would
|
|
46
|
-
remove a version-pinned tool from a repo that can still run it, and nothing would detect the loss
|
|
47
|
-
because resolution never read those bytes. Phase 6's twin collapse is therefore closed as
|
|
48
|
-
already answered, at `a9983c8`.
|
|
49
|
-
|
|
50
|
-
3. **`local.properties` stays tracked.** Both `repo-05` and `repo-03` commit it, and both
|
|
51
|
-
`.gitignore` files have the conventional `# local.properties` line commented out at lines 22 and
|
|
52
|
-
24 — a deliberate override of Android's own convention, not an oversight. Untracking them would
|
|
53
|
-
be a house-style change made while closing a plan, and a build machine that checks out without
|
|
54
|
-
an SDK would stop building. It stays, and plan 0001 says so, so the next sweep does not
|
|
55
|
-
rediscover it as a defect.
|
|
56
|
-
|
|
57
|
-
## Consequences
|
|
58
|
-
|
|
59
|
-
Row 3 reads "done, four of eight" rather than "partial". Phase 6's twin item reads "superseded"
|
|
60
|
-
rather than "outstanding". The estate keeps two `local.properties` files whose `sdk.dir` names a
|
|
61
|
-
developer's home directory, by decision.
|
|
62
|
-
|
|
63
|
-
The registry's byte counts moved with the two skill bodies corrected in the same pass, because a
|
|
64
|
-
declared source's identity is its size (ADR-015): `cloudflare` 384277 → 384869 and
|
|
65
|
-
`production-quality-review` 5869 → 5904. That changed the provenance digest (ADR-032), so all
|
|
66
|
-
fourteen adopted repos were re-stamped afterwards rather than left pinning a value this tool can no
|
|
67
|
-
longer produce.
|
|
68
|
-
|
|
69
|
-
## Rejected
|
|
70
|
-
|
|
71
|
-
**Authoring the seven agent policies.** The plan's one-line summaries are not enough to write seven
|
|
72
|
-
files of rule text, and these rules reach every adopted repo. This is ADR-030's exact shape: an
|
|
73
|
-
unmade decision is not a gap to fill by invention.
|
|
74
|
-
|
|
75
|
-
**Collapsing `graphify` and `parallel-feature-team`.** Both are declared `duplicate` with matching
|
|
76
|
-
byte counts, so they look like the obvious candidates. `graphify` is `vendored: true` — an
|
|
77
|
-
84 534-byte, ten-file, version-pinned tool present in both one repository on purpose. Deleting
|
|
78
|
-
repo-14's copy would leave nothing to detect the loss, because resolution only ever read the
|
|
79
|
-
canonical copy.
|
|
80
|
-
|
|
81
|
-
**Untracking `local.properties`.** An Android SDK path is machine state, and two repos chose to
|
|
82
|
-
share it. That is the repo's call to reverse, not a plan's defect sweep.
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
simt-affects: ["docs/reviews/0002-full-review-adoption-and-public-release.md"]
|
|
3
|
-
simt-supersedes: []
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# ADR-102 — the two rows review 0002 left queued are closed by ruling
|
|
7
|
-
|
|
8
|
-
Status: **accepted** · 2026-10-28
|
|
9
|
-
|
|
10
|
-
## Context
|
|
11
|
-
|
|
12
|
-
Review 0002's §3 tracker is the only tracker of slice state, and two rows in it
|
|
13
|
-
still read Queued while everything around them shipped: S38 (workspace token
|
|
14
|
-
cost) and S39 (estate policy as a pack). Both rows' evidence cells are
|
|
15
|
-
palimpsests — notes from three different landings disagreeing about what is
|
|
16
|
-
still open — so reading them as a work list sends the next reader to build what
|
|
17
|
-
is already built. Closing plan 0001 meant the same audit, and ADR-033 is the
|
|
18
|
-
precedent: rows closed by saying what shipped, what was refused by ruling, and
|
|
19
|
-
where the remainder lives, not by authoring what they asked for.
|
|
20
|
-
|
|
21
|
-
## Ruling
|
|
22
|
-
|
|
23
|
-
1. **S38 is Complete.** The instrument shipped (`simt tokens`, ADR-064), the
|
|
24
|
-
declaration shipped (`budget.bytes` beside `limits` and `gates`, ADR-072),
|
|
25
|
-
and the gate raises it as a finding through one `budgetFinding` read from
|
|
26
|
-
one place by both `tokens` and `check` — the "still open" clause the row's
|
|
27
|
-
first note ends on. Kernel-line packing shipped with it (R07's `mode:
|
|
28
|
-
kernel`, R08's budget compilation). Blocking enforcement is refused, not
|
|
29
|
-
outstanding: a gate that fails a repository for exceeding a ceiling it set
|
|
30
|
-
itself, when nothing was cut, has no repair except lowering the declared
|
|
31
|
-
number (ADR-072). The one genuine remainder is the kernel-default
|
|
32
|
-
compliance study, and it is owned by plan 0003 R07's own note, not by this
|
|
33
|
-
row — the study gates making `kernel` the default, which no row asks to
|
|
34
|
-
decide here.
|
|
35
|
-
|
|
36
|
-
2. **S39 is Complete.** The mechanism shipped (packs vendored and digest-pinned,
|
|
37
|
-
ADR-065; measured ΔR, plan 0003 R45). P4's pack-carries-the-registry line was
|
|
38
|
-
superseded by ADR-061, which made the registry workspace data, so there is
|
|
39
|
-
no registry-carrying pack to build. The remaining half — moving estate
|
|
40
|
-
policy into a pack — is the estate's own commit: no estate content may enter
|
|
41
|
-
this repository, and a row that waits on another repository's commit is
|
|
42
|
-
closed by recording whose commit it waits on.
|
|
43
|
-
|
|
44
|
-
## Consequences
|
|
45
|
-
|
|
46
|
-
- The §3 tracker reads Complete on both rows, with this record as the evidence,
|
|
47
|
-
so the next audit starts from rulings rather than from mid-flight notes.
|
|
48
|
-
- A reader asking for a blocking budget gate or a registry-carrying pack gets
|
|
49
|
-
the refusal and its reason instead of an open row that looks like a promise.
|