simt-dev 0.2.2 → 0.3.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 +37 -0
- package/bin/simt-dev.mjs +21 -2
- package/docs/decisions/ADR-074-a-rule-and-a-decision-are-readable-by-uri.md +99 -0
- package/docs/decisions/ADR-075-nothing-leaves-the-machine.md +56 -0
- package/docs/decisions/ADR-076-a-change-to-what-agents-read-is-measured.md +70 -0
- package/docs/decisions/ADR-077-the-command-card-leads-the-region-and-reads-committed-evidence.md +91 -0
- package/docs/decisions/ADR-078-a-line-the-repository-already-proves-does-not-ship.md +117 -0
- package/docs/decisions/ADR-079-the-rules-are-enforced-at-the-tool-call-from-one-policy-table.md +162 -0
- package/docs/decisions/ADR-080-the-tools-own-names-are-not-repositories-and-neither-reader-decides-by-shape.md +99 -0
- package/docs/decisions/ADR-081-a-tracker-is-read-through-the-repositorys-template.md +36 -0
- package/docs/decisions/ADR-082-the-tracker-override-is-a-manifest-key.md +35 -0
- package/package.json +2 -1
- package/src/agenthooks/decide.mjs +123 -0
- package/src/agenthooks/policy.mjs +78 -0
- package/src/agenthooks/render.mjs +240 -0
- package/src/argv.mjs +36 -1
- package/src/bench/ablate.mjs +630 -0
- package/src/bench/adapters.json +149 -0
- package/src/bench/grade.mjs +229 -0
- package/src/bench/mine.mjs +811 -0
- package/src/bench/probes.mjs +1350 -0
- package/src/bench/report.mjs +899 -0
- package/src/bench/run.mjs +1002 -0
- package/src/commands/agenthook.mjs +282 -0
- package/src/commands/bench.mjs +359 -0
- package/src/commands/harness.mjs +156 -5
- package/src/commands/help.mjs +58 -1
- package/src/commands/index.mjs +25 -1
- package/src/commands/plan.mjs +99 -0
- package/src/commands/tokens.mjs +31 -0
- package/src/decisions/index.mjs +23 -5
- package/src/kernel/card.mjs +254 -0
- package/src/kernel/inferability.mjs +188 -0
- package/src/mcp/resources.mjs +157 -0
- package/src/mcp/server.mjs +37 -6
- package/src/mcp/tools.mjs +84 -0
- package/src/measure/context.mjs +177 -6
- package/src/measure/loaderView.mjs +435 -0
- package/src/measure/session.mjs +267 -0
- package/src/plan/index.mjs +650 -0
- package/src/plan/template.mjs +331 -0
- package/src/projection/loaders.mjs +689 -0
- package/src/projection/manifest.mjs +26 -3
- package/src/projection/rules.mjs +80 -5
- package/src/projection/surfaces.mjs +22 -8
- package/src/toolwords.mjs +69 -0
- package/templates/plan/tracker.json +36 -0
|
@@ -67,8 +67,12 @@ export async function manifestPath(root) {
|
|
|
67
67
|
* real is the recorded SHA under `projection`.
|
|
68
68
|
*/
|
|
69
69
|
export const MANIFEST_COMMENT = {
|
|
70
|
-
$comment:
|
|
71
|
-
`Written and maintained by simt-dev.
|
|
70
|
+
$comment: [
|
|
71
|
+
`Written and maintained by simt-dev. \`${CLI} sync --yes\` regenerates the projected rules.`,
|
|
72
|
+
`\`${CLI} init\` refreshes the SHA and keeps every key it does not write, so a value you add here survives a re-init.`,
|
|
73
|
+
`\`plans\` is such a key: it selects the tracker template and the directories \`${CLI} plan\` reads.`,
|
|
74
|
+
'Omit it and the tracker template applies, which reads docs/plans and docs/reviews.',
|
|
75
|
+
].join(' '),
|
|
72
76
|
};
|
|
73
77
|
|
|
74
78
|
/**
|
|
@@ -418,7 +422,7 @@ export function buildManifest({ root, stack = [], sha, version, existing = null,
|
|
|
418
422
|
? { ...planned.gates, commands: planned.commands }
|
|
419
423
|
: planned.gates;
|
|
420
424
|
|
|
421
|
-
|
|
425
|
+
const owned = {
|
|
422
426
|
...MANIFEST_COMMENT,
|
|
423
427
|
id: existing?.id ?? defaultId(root),
|
|
424
428
|
stack: existing?.stack?.length ? existing.stack : stack,
|
|
@@ -473,6 +477,25 @@ export function buildManifest({ root, stack = [], sha, version, existing = null,
|
|
|
473
477
|
// adopted. See src/package-version.mjs.
|
|
474
478
|
projection: { simtDevSha: sha ?? null, version: version ?? null },
|
|
475
479
|
};
|
|
480
|
+
return withRepoKeys(existing, owned);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Keys this writer does not recognise belong to the repository.
|
|
485
|
+
*
|
|
486
|
+
* A re-init used to return only the object above, so anything else in the file
|
|
487
|
+
* disappeared. `plans` is the tracker overlay, and it is not the only key a
|
|
488
|
+
* repository might have added. Owned keys stay in the order the writer emits
|
|
489
|
+
* them; an unknown key is copied after them, in the order the file had it.
|
|
490
|
+
*/
|
|
491
|
+
function withRepoKeys(existing, owned) {
|
|
492
|
+
if (!existing || typeof existing !== 'object' || Array.isArray(existing)) return owned;
|
|
493
|
+
const kept = {};
|
|
494
|
+
for (const key of Object.keys(existing)) {
|
|
495
|
+
if (Object.hasOwn(owned, key)) continue;
|
|
496
|
+
kept[key] = existing[key];
|
|
497
|
+
}
|
|
498
|
+
return { ...owned, ...kept };
|
|
476
499
|
}
|
|
477
500
|
|
|
478
501
|
/**
|
package/src/projection/rules.mjs
CHANGED
|
@@ -30,6 +30,8 @@ import { loadMdcRules, renderMdc } from './mdc.mjs';
|
|
|
30
30
|
import { foldsRules, frontmatterBlock, resolveSurfaces, rulePath, canScope } from './surfaces.mjs';
|
|
31
31
|
import { ruleTargets } from './targets.mjs';
|
|
32
32
|
import { manifestPath, readManifest } from './manifest.mjs';
|
|
33
|
+
import { commandCard, CARD_LIMIT_BYTES } from '../kernel/card.mjs';
|
|
34
|
+
import { filterInferable, inferableDefect, inferableProofs } from '../kernel/inferability.mjs';
|
|
33
35
|
import { BASELINES } from './baselines.mjs';
|
|
34
36
|
import { CLI } from '../name.mjs';
|
|
35
37
|
import { PACK_DIR, resolvePacks } from '../packs.mjs';
|
|
@@ -408,6 +410,19 @@ async function ruleBodies(root, manifest, flags) {
|
|
|
408
410
|
return { resolved, bodies, problems, notes, skipped };
|
|
409
411
|
}
|
|
410
412
|
|
|
413
|
+
/**
|
|
414
|
+
* The command card for a repository and the evidence its markers may be proved by.
|
|
415
|
+
*
|
|
416
|
+
* Both the region and the package regions need the proofs, and they must be the same set:
|
|
417
|
+
* a rule dropped at the root and kept in a package would be one rule treated as two. The
|
|
418
|
+
* card comes back alongside because the caller that needs the proofs also needs the card's
|
|
419
|
+
* bytes, and building it twice would read the manifest twice for no second answer.
|
|
420
|
+
*/
|
|
421
|
+
async function inferableEvidence(root, { stack, facts, manifest }) {
|
|
422
|
+
const card = await commandCard({ root, manifest, stack, facts });
|
|
423
|
+
return { card, proofs: inferableProofs({ facts, stack, card, manifest }) };
|
|
424
|
+
}
|
|
425
|
+
|
|
411
426
|
/**
|
|
412
427
|
* The one place the desired region is computed. `plan` (which writes) and
|
|
413
428
|
* `checkTarget` (which gates) both go through this, so they cannot disagree
|
|
@@ -469,8 +484,50 @@ export async function desiredRegion(root, flags) {
|
|
|
469
484
|
if (mode === 'kernel' && (r.error !== null || !KERNEL_CLASSES.has(r.priority))) deferred.push(r);
|
|
470
485
|
else packed.push(r);
|
|
471
486
|
}
|
|
472
|
-
|
|
473
|
-
|
|
487
|
+
// The command card leads the region. The exact commands for this repository are the
|
|
488
|
+
// bytes a reader cannot infer from rules that are identical everywhere (plan 0003 R10),
|
|
489
|
+
// so they go first — above even the core rules. It is derived from committed evidence
|
|
490
|
+
// only, in one place, so `plan` and `checkTarget` cannot disagree about it and two
|
|
491
|
+
// machines render it byte-identically.
|
|
492
|
+
const { card, proofs } = await inferableEvidence(root, { stack, facts, manifest });
|
|
493
|
+
if (card.dropped.length > 0) {
|
|
494
|
+
// Reported where every other projection note is reported. A card that lost a command
|
|
495
|
+
// in silence is a card a reader cannot tell is incomplete.
|
|
496
|
+
notes.push(
|
|
497
|
+
`command card: dropped ${card.dropped.join(', ')} to stay within ${CARD_LIMIT_BYTES} bytes`,
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
// Inferability (R09). A marked line leaves when the repository proves the evidence it
|
|
501
|
+
// names. `shipped` is a separate list from `packed` because they are separate decisions:
|
|
502
|
+
// the kernel is about which classes cost a reader nothing to skip, and this is about
|
|
503
|
+
// which lines the reader already knows.
|
|
504
|
+
const inferable = [];
|
|
505
|
+
const shipped = [];
|
|
506
|
+
for (const r of packed) {
|
|
507
|
+
const filtered = filterInferable(r.body, proofs);
|
|
508
|
+
if (!filtered.changed) {
|
|
509
|
+
shipped.push(r);
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
for (const x of filtered.removed) inferable.push({ rule: r.id, text: x.text, expr: x.expr });
|
|
513
|
+
shipped.push({ ...r, body: filtered.body });
|
|
514
|
+
}
|
|
515
|
+
let body = card.text ? `${card.text}\n\n${render(shipped)}` : render(shipped);
|
|
516
|
+
let sources = shipped.map((r) => r.source);
|
|
517
|
+
if (inferable.length > 0) {
|
|
518
|
+
// Never silent, and never a heading. A line that left with no trace repeats the
|
|
519
|
+
// mistake ADR-073 rejected for deferred rules, and a `## Dropped` section spends region
|
|
520
|
+
// bytes on lines the reader is being told not to read. A count they can act on, plus —
|
|
521
|
+
// with `--explain` — the evidence behind each one, is the third option.
|
|
522
|
+
notes.push(
|
|
523
|
+
`inferability: dropped ${inferable.length} line(s) this repository already proves; ${CLI} sync --explain names each one and the evidence behind it`,
|
|
524
|
+
);
|
|
525
|
+
if (flags?.explain === true) {
|
|
526
|
+
for (const x of inferable) {
|
|
527
|
+
notes.push(`inferable: ${x.rule} dropped "${x.text}" — proved by ${x.expr}`);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
474
531
|
const folded = [];
|
|
475
532
|
|
|
476
533
|
// The pointer. A reader who opens AGENTS.md and does not find a profile they know is
|
|
@@ -523,6 +580,10 @@ export async function desiredRegion(root, flags) {
|
|
|
523
580
|
folded,
|
|
524
581
|
mode,
|
|
525
582
|
deferred: deferred.map((r) => r.id),
|
|
583
|
+
// The marked lines that left, each with the evidence that dropped it. The region is
|
|
584
|
+
// written without them; `--explain` is what reads this list, so a removal is reportable
|
|
585
|
+
// without spending region bytes and without a second computation to find it.
|
|
586
|
+
inferable,
|
|
526
587
|
// The profiles that left the region, and the surfaces that accepted them as files.
|
|
527
588
|
// `plan` writes them and `checkTarget` gates them through this list, so a projected
|
|
528
589
|
// file and a drifted one are decided by the same computation.
|
|
@@ -643,6 +704,7 @@ export async function loadRules(root = null) {
|
|
|
643
704
|
for (const item of collected) {
|
|
644
705
|
const text = await readFile(join(item.dir, item.file), 'utf8');
|
|
645
706
|
const meta = parseFrontmatter(text);
|
|
707
|
+
const body = stripFrontmatter(text);
|
|
646
708
|
out.push({
|
|
647
709
|
file: item.file,
|
|
648
710
|
origin: item.origin,
|
|
@@ -654,8 +716,12 @@ export async function loadRules(root = null) {
|
|
|
654
716
|
scope: Array.isArray(meta['simt-scope']) ? meta['simt-scope'] : null,
|
|
655
717
|
priority: meta['simt-priority'] ?? null,
|
|
656
718
|
bytes: Buffer.byteLength(text),
|
|
657
|
-
body
|
|
658
|
-
|
|
719
|
+
body,
|
|
720
|
+
// A marker naming evidence the tool cannot check is a broken rule like any other, so
|
|
721
|
+
// it travels the field that already reaches `check` and `rules list`. The
|
|
722
|
+
// frontmatter defect wins, because a rule that cannot say what it applies to cannot
|
|
723
|
+
// be reasoned about at all.
|
|
724
|
+
error: describeDefect(meta) ?? inferableDefect(body),
|
|
659
725
|
});
|
|
660
726
|
}
|
|
661
727
|
return out;
|
|
@@ -691,12 +757,21 @@ export async function packageRegions(root, flags, rootProfiles = null) {
|
|
|
691
757
|
|
|
692
758
|
const rules = await loadRules(root);
|
|
693
759
|
const sha = await provenanceDigest();
|
|
760
|
+
// The ROOT's proofs, not the package's. The rule set is the root's, so which lines it
|
|
761
|
+
// already proves is a fact about the repository rather than about the directory a file
|
|
762
|
+
// lands in — filtering a package by the package's own facts would keep a rule here and
|
|
763
|
+
// drop it one directory up, which is the same rule treated as two.
|
|
764
|
+
const { proofs } = await inferableEvidence(root, await fingerprint(root));
|
|
694
765
|
const regions = [];
|
|
695
766
|
for (const pkg of packages) {
|
|
696
767
|
const fp = await fingerprint(join(root, pkg.path));
|
|
697
768
|
const { selected } = select(rules, fp.stack ?? [], fp.facts ?? []);
|
|
698
769
|
const delta = selected.filter((r) => !shared.includes(r.id));
|
|
699
770
|
if (delta.length === 0) continue;
|
|
771
|
+
const shipped = delta.map((r) => {
|
|
772
|
+
const filtered = filterInferable(r.body, proofs);
|
|
773
|
+
return filtered.changed ? { ...r, body: filtered.body } : r;
|
|
774
|
+
});
|
|
700
775
|
regions.push({
|
|
701
776
|
path: pkg.path,
|
|
702
777
|
ecosystem: pkg.ecosystem,
|
|
@@ -706,7 +781,7 @@ export async function packageRegions(root, flags, rootProfiles = null) {
|
|
|
706
781
|
// One sentence, because the file's whole purpose is to be found by a reader
|
|
707
782
|
// who does not know it exists: without it, a nested region reads as a
|
|
708
783
|
// complete set of rules and the shared half looks forgotten.
|
|
709
|
-
body: `The rules this package selects on its own. Everything the repository shares with its other packages is in the region at the root.\n\n${render(
|
|
784
|
+
body: `The rules this package selects on its own. Everything the repository shares with its other packages is in the region at the root.\n\n${render(shipped)}`,
|
|
710
785
|
meta: { sha, sources: delta.map((r) => r.source) },
|
|
711
786
|
});
|
|
712
787
|
}
|
|
@@ -91,13 +91,13 @@ export const SURFACES = [
|
|
|
91
91
|
id: 'opencode',
|
|
92
92
|
label: 'opencode (AGENTS.md + .agents/skills)',
|
|
93
93
|
readers:
|
|
94
|
-
'
|
|
94
|
+
'OpenCode v2, and AGENTS.md is also read natively by Codex, Cursor, Windsurf, Zed, Amp, Jules, Copilot CLI and the DeepSeek harness; Claude Code falls back to it, Antigravity and Gemini CLI accept GEMINI.md as an equal name',
|
|
95
95
|
primary: 'AGENTS.md',
|
|
96
96
|
kind: 'region',
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
// generated region, and the other rows are how a rule reaches an editor that
|
|
97
|
+
// OpenCode v2 documents no per-rule directory. It reads AGENTS.md only — every
|
|
98
|
+
// file from the workspace up to the project root, combined — and it does not fall
|
|
99
|
+
// back to CLAUDE.md. So `foldRules`: this surface carries every rule inside the
|
|
100
|
+
// one generated region, and the other rows are how a rule reaches an editor that
|
|
101
101
|
// cannot read AGENTS.md. Writing .opencode/rules/*.md would be a guess, and a
|
|
102
102
|
// guessed path is the failure this table exists to prevent.
|
|
103
103
|
rule: null,
|
|
@@ -118,7 +118,7 @@ export const SURFACES = [
|
|
|
118
118
|
},
|
|
119
119
|
],
|
|
120
120
|
verified:
|
|
121
|
-
'
|
|
121
|
+
'OpenCode v2 instructions and skills docs (opencode.ai/v2/docs, read 2026-10-14) — AGENTS.md only, combined from the workspace up to the project root; CLAUDE.md is not a fallback; skills at .opencode/skills/, .claude/skills/ and .agents/skills/<name>/SKILL.md, with the model shown name and description',
|
|
122
122
|
note:
|
|
123
123
|
'.agents/skills is one of the three skill locations opencode documents, so the same directory serves the standard surface without a symlink.',
|
|
124
124
|
},
|
|
@@ -286,8 +286,12 @@ export const POINTERS = [
|
|
|
286
286
|
to: 'AGENTS.md',
|
|
287
287
|
readers: 'Gemini CLI, Antigravity',
|
|
288
288
|
verified:
|
|
289
|
-
'Google Antigravity docs, "Rules": workspace rules are AGENTS.md or GEMINI.md in the root or any subdirectory; Copilot CLI also discovers GEMINI.md'
|
|
290
|
-
|
|
289
|
+
'Google Antigravity docs, "Rules": workspace rules are AGENTS.md or GEMINI.md in the root or any subdirectory; Copilot CLI also discovers GEMINI.md. ' +
|
|
290
|
+
'Gemini CLI, read today: its own docs (core memory index, and the configuration reference) say the memory service loads GEMINI.md hierarchically from the working directory up to the project root and the home directory and also into subdirectories, and that `context.fileName` in settings.json "accepts either a single string or an array of strings" naming the context file(s) to load, default undefined',
|
|
291
|
+
note:
|
|
292
|
+
'Covers Gemini CLI and gives Antigravity a second, equally-native name for the same file. ' +
|
|
293
|
+
'A repository can also point Gemini CLI at AGENTS.md itself with `context.fileName: ["AGENTS.md"]` and need no pointer at all, ' +
|
|
294
|
+
'which is the reason there is no separate Gemini CLI row here: this pointer and a native row would be two ways to hand one reader the same rule.',
|
|
291
295
|
},
|
|
292
296
|
];
|
|
293
297
|
|
|
@@ -399,6 +403,16 @@ export const CANDIDATE_DIRS = [
|
|
|
399
403
|
{ dir: '.continue', harness: 'Continue' },
|
|
400
404
|
{ dir: '.amp', harness: 'Amp' },
|
|
401
405
|
{ dir: '.goose', harness: 'Goose' },
|
|
406
|
+
// Home directory, seen only with `harness detect --user`. DeepSeek keeps user data
|
|
407
|
+
// under ~/.dsh and reads the repository's AGENTS.md; it has no project config
|
|
408
|
+
// directory and no per-rule file to project (ADR-067).
|
|
409
|
+
{
|
|
410
|
+
dir: '.dsh',
|
|
411
|
+
harness: 'DeepSeek harness',
|
|
412
|
+
readsAgentsMd: 'all',
|
|
413
|
+
verified:
|
|
414
|
+
'DeepSeek harness agent-instructions docs (read 2026-10-14): user data lives at $DSH_HOME or ~/.dsh, and the project chain is AGENTS.md',
|
|
415
|
+
},
|
|
402
416
|
];
|
|
403
417
|
|
|
404
418
|
/**
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// The `simt-` names this tool owns, and the records that argue about the prefix itself.
|
|
2
|
+
//
|
|
3
|
+
// Two readers need to tell this tool's own vocabulary from a repository in the estate
|
|
4
|
+
// these records were written about: the decision reader, which redacts the second kind
|
|
5
|
+
// and names it, and the guard on shipped text, which refuses to ship it. Both answered
|
|
6
|
+
// the question with a shape — `simt-` followed by anything but `dev` — and the shape is
|
|
7
|
+
// false about the tool's own names, which the corpus prints. `rules why ADR-073` reported
|
|
8
|
+
// that the ruling surveyed `simt-priority` as a repository, and printed the same word as
|
|
9
|
+
// `a repository: hard | command | convention | style` where the record says
|
|
10
|
+
// `simt-priority: hard | command | convention | style`: a reader with the tool and not
|
|
11
|
+
// the estate got a sentence with no subject, from the command whose whole job is to
|
|
12
|
+
// explain a citation.
|
|
13
|
+
//
|
|
14
|
+
// The words are derived from the modules that own them (ADR-022) rather than listed
|
|
15
|
+
// here, because a second list is a list that drifts and then forgives. Both readers read
|
|
16
|
+
// this one, and a name added to a frontmatter vocabulary reaches both without an edit.
|
|
17
|
+
import { KNOWN_KEYS } from './projection/mdc.mjs';
|
|
18
|
+
import { PROFILE_KEYS } from './projection/rules.mjs';
|
|
19
|
+
import { INFERABLE_MARKER } from './kernel/inferability.mjs';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* `simt-` names that belong to this tool and never to a repository.
|
|
23
|
+
*
|
|
24
|
+
* The last three are not frontmatter: they are names this tool writes into files it
|
|
25
|
+
* adopts a repository into — the package, the gate file it puts in `.github`, and the
|
|
26
|
+
* hooks directory it points `core.hooksPath` at. `simt-dev` itself is here for the same
|
|
27
|
+
* reason the estate pattern exempts it, and stating it once here is what stops the two
|
|
28
|
+
* readers from disagreeing about the tool's own name.
|
|
29
|
+
*/
|
|
30
|
+
export const TOOL_WORDS = new Set([
|
|
31
|
+
...KNOWN_KEYS,
|
|
32
|
+
...PROFILE_KEYS,
|
|
33
|
+
INFERABLE_MARKER,
|
|
34
|
+
'simt-dev',
|
|
35
|
+
'simt-dev-check',
|
|
36
|
+
'simt-hooks',
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Records whose subject is the prefix itself, so the prefixed names in them are the
|
|
41
|
+
* argument rather than a citation.
|
|
42
|
+
*
|
|
43
|
+
* Each entry is a file and a reason, never a repository. That distinction is the whole
|
|
44
|
+
* point: an exemption says "this document discusses the namespace", which is a fact
|
|
45
|
+
* about this tool and carries nothing about whoever uses it. A vocabulary cannot settle
|
|
46
|
+
* these two — a hypothetical key in an argument and a repository name are the same shape
|
|
47
|
+
* — so the file is exempted where the word cannot be.
|
|
48
|
+
*
|
|
49
|
+
* The guard fails on a stale entry: each one must still be tripping the rule, so an
|
|
50
|
+
* exemption that stops being needed is removed rather than kept as a precedent.
|
|
51
|
+
*/
|
|
52
|
+
export const PREFIX_ARGUING = new Map([
|
|
53
|
+
[
|
|
54
|
+
'ADR-022-a-key-simt-dev-reads-is-prefixed.md',
|
|
55
|
+
'is the record arguing for the prefix, using hypothetical keys as examples',
|
|
56
|
+
],
|
|
57
|
+
[
|
|
58
|
+
'ADR-026-a-config-without-a-region-is-a-file-the-repo-owns.md',
|
|
59
|
+
'names one prefixed key to record Dependabot rejecting it',
|
|
60
|
+
],
|
|
61
|
+
[
|
|
62
|
+
'ADR-080-the-tools-own-names-are-not-repositories-and-neither-reader-decides-by-shape.md',
|
|
63
|
+
'quotes the prefixed names it measured, which are its evidence rather than a citation',
|
|
64
|
+
],
|
|
65
|
+
[
|
|
66
|
+
'ADR-081-a-tracker-is-read-through-the-repositorys-template.md',
|
|
67
|
+
'names the plan overlay file the next record withdraws, and that filename has the shape of a repository',
|
|
68
|
+
],
|
|
69
|
+
]);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "Tracker format delivered with the tool. A repository replaces any section with the plans key in simt.json; a section it omits stays as written here. directories are the only places read, and each word is what a dependency uses to name that place (\"review 0002 S41\"). Headers are tried in the order written. Only a state marked satisfies unblocks a slice that depends on it. ready is the state plan next returns. dependency.group matches a wave of rows; wave names that table's columns.",
|
|
3
|
+
"name": "tracker",
|
|
4
|
+
"directories": [
|
|
5
|
+
{ "kind": "plans", "word": "plan", "path": "docs/plans" },
|
|
6
|
+
{ "kind": "reviews", "word": "review", "path": "docs/reviews" }
|
|
7
|
+
],
|
|
8
|
+
"id": {
|
|
9
|
+
"pattern": "^[A-Za-z]{0,4}[- ]?[0-9]{1,4}$",
|
|
10
|
+
"headers": ["id", "#", "step", "item", "slice", "task"]
|
|
11
|
+
},
|
|
12
|
+
"columns": {
|
|
13
|
+
"state": ["state", "status", "done when"],
|
|
14
|
+
"depends": ["depends on", "depends", "dependency", "dependencies", "deps", "needs"],
|
|
15
|
+
"title": ["slice", "deliverable", "title", "summary", "description", "content"],
|
|
16
|
+
"evidence": ["sha / evidence", "sha/evidence", "evidence", "sha", "notes", "note"]
|
|
17
|
+
},
|
|
18
|
+
"states": [
|
|
19
|
+
{ "id": "queued", "write": "Queued", "read": ["queued"] },
|
|
20
|
+
{ "id": "in-progress", "write": "In progress", "read": ["in progress", "in-progress"] },
|
|
21
|
+
{ "id": "complete", "write": "Complete", "read": ["complete", "completed", "done"], "satisfies": true },
|
|
22
|
+
{ "id": "accepted", "write": "Accepted", "read": ["accepted"] }
|
|
23
|
+
],
|
|
24
|
+
"ready": "queued",
|
|
25
|
+
"dependency": {
|
|
26
|
+
"empty": ["none", "null", "n/a", "n.a.", "na", "—", "-", "–"],
|
|
27
|
+
"separators": ",;",
|
|
28
|
+
"notes": true,
|
|
29
|
+
"group": "^waves?\\s+(.+)$"
|
|
30
|
+
},
|
|
31
|
+
"wave": {
|
|
32
|
+
"label": ["wave"],
|
|
33
|
+
"members": ["slices"]
|
|
34
|
+
},
|
|
35
|
+
"section": { "depth": 3 }
|
|
36
|
+
}
|