@ikon85/agent-workflow-kit 0.42.1 → 0.44.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.
Files changed (133) hide show
  1. package/.agents/skills/grill-me/SKILL.md +3 -1
  2. package/.agents/skills/grill-with-docs/SKILL.md +3 -1
  3. package/.agents/skills/kit-update/SKILL.md +9 -11
  4. package/.agents/skills/orchestrate-wave/SKILL.md +8 -8
  5. package/.agents/skills/setup-workflow/SKILL.md +20 -18
  6. package/.agents/skills/setup-workflow/board-sync.md +4 -1
  7. package/.agents/skills/setup-workflow/workflow-advisories.md +3 -2
  8. package/.agents/skills/setup-workflow/worktree-lifecycle.md +36 -31
  9. package/.agents/skills/to-issues/SKILL.md +43 -0
  10. package/.agents/skills/wrapup/SKILL.md +98 -54
  11. package/.claude/skills/grill-me/SKILL.md +3 -1
  12. package/.claude/skills/grill-me-codex/SKILL.md +3 -1
  13. package/.claude/skills/grill-with-docs/SKILL.md +3 -1
  14. package/.claude/skills/grill-with-docs-codex/SKILL.md +3 -1
  15. package/.claude/skills/kit-update/SKILL.md +9 -11
  16. package/.claude/skills/orchestrate-wave/SKILL.md +8 -8
  17. package/.claude/skills/setup-workflow/SKILL.md +20 -18
  18. package/.claude/skills/setup-workflow/board-sync.md +4 -1
  19. package/.claude/skills/setup-workflow/workflow-advisories.md +3 -2
  20. package/.claude/skills/setup-workflow/worktree-lifecycle.md +36 -31
  21. package/.claude/skills/to-issues/SKILL.md +43 -0
  22. package/.claude/skills/wrapup/SKILL.md +98 -54
  23. package/README.md +268 -7
  24. package/agent-workflow-kit.package.json +42 -42
  25. package/package.json +16 -2
  26. package/scripts/marker_lib.py +3 -0
  27. package/scripts/profile_globs.py +26 -45
  28. package/scripts/release-delta-guard.mjs +29 -4
  29. package/scripts/worktree-lifecycle/README.md +57 -154
  30. package/scripts/worktree-lifecycle/capabilities.json +2 -3
  31. package/scripts/worktree-lifecycle/classify.py +480 -0
  32. package/scripts/worktree-lifecycle/cleanup.py +9 -29
  33. package/scripts/worktree-lifecycle/core.py +68 -1289
  34. package/scripts/worktree-lifecycle/ignore_seed.py +1 -1
  35. package/scripts/worktree-lifecycle/profile.py +46 -29
  36. package/scripts/worktree-lifecycle/setup.py +7 -19
  37. package/scripts/wrapup-land.py +1292 -545
  38. package/src/consumer-migrations.json +1 -16
  39. package/src/lib/bundle.mjs +31 -1
  40. package/docs/adr/0001-consumer-divergence-policy.md +0 -53
  41. package/docs/adr/0002-capability-gated-orchestration.md +0 -70
  42. package/docs/adr/0003-kit-core-and-project-extension-lifecycle.md +0 -63
  43. package/docs/adr/0004-release-intent-is-a-version-tag.md +0 -64
  44. package/docs/adr/0005-to-issues-is-the-planning-facade.md +0 -42
  45. package/docs/adr/0006-routing-knowledge-access-and-policy-are-separate.md +0 -95
  46. package/docs/adr/0007-session-teardown-requires-provenance-bound-ownership.md +0 -88
  47. package/docs/adr/0008-planning-ignore-rules-are-offered-never-installed.md +0 -84
  48. package/docs/adr/0009-teardown-authority-is-stateless-repository-classification.md +0 -101
  49. package/docs/adr/0010-model-roster-replaces-the-optimization-dial.md +0 -87
  50. package/docs/agents/board-sync.md +0 -74
  51. package/docs/agents/code-review.md +0 -14
  52. package/docs/agents/domain.md +0 -7
  53. package/docs/agents/issue-tracker.md +0 -23
  54. package/docs/agents/skills/local-ci.md +0 -90
  55. package/docs/agents/skills/orchestrate-wave.md +0 -166
  56. package/docs/agents/skills/spec-self-critique.md +0 -18
  57. package/docs/agents/triage-labels.md +0 -10
  58. package/docs/agents/workflow-capabilities.json +0 -28
  59. package/docs/conventions/spec-completeness.md +0 -23
  60. package/docs/research/agent-task-taxonomy-benchmark-coverage.md +0 -451
  61. package/docs/research/benchlm-routing-source.md +0 -198
  62. package/docs/research/consumer-owned-protocol-files.md +0 -238
  63. package/docs/research/frontend-agent-benchmarks.md +0 -282
  64. package/docs/research/model-effort-routing-benchmarks.md +0 -261
  65. package/docs/research/provider-neutral-agent-routing.md +0 -207
  66. package/docs/research/wave-152-consumer-acceptance.md +0 -98
  67. package/docs/research/wave-43-script-hook-census.md +0 -128
  68. package/scripts/build-kit.mjs +0 -117
  69. package/scripts/build-kit.test.mjs +0 -204
  70. package/scripts/census/census.test.mjs +0 -252
  71. package/scripts/census/state.test.mjs +0 -15
  72. package/scripts/census/transaction.test.mjs +0 -103
  73. package/scripts/census-contract.test.mjs +0 -227
  74. package/scripts/check-kit-staleness.mjs +0 -45
  75. package/scripts/check-kit-staleness.test.mjs +0 -19
  76. package/scripts/codex-exec-scenarios/fake-codex.mjs +0 -152
  77. package/scripts/codex-exec.test.mjs +0 -871
  78. package/scripts/grill-census-wiring-guard.mjs +0 -87
  79. package/scripts/grill-census-wiring-guard.test.mjs +0 -107
  80. package/scripts/kit-release.test.mjs +0 -273
  81. package/scripts/kit-update-pr.test.mjs +0 -151
  82. package/scripts/lib/audit-refs.mjs +0 -146
  83. package/scripts/lib/scrub.mjs +0 -113
  84. package/scripts/lib/scrub.test.mjs +0 -187
  85. package/scripts/memory-lifecycle/memory-lifecycle.test.mjs +0 -170
  86. package/scripts/portability_profile_scan.py +0 -53
  87. package/scripts/release-delta-guard.test.mjs +0 -151
  88. package/scripts/release-parity.test.mjs +0 -90
  89. package/scripts/release-state.test.mjs +0 -360
  90. package/scripts/test_anchor_table.py +0 -69
  91. package/scripts/test_board_bootstrap.py +0 -348
  92. package/scripts/test_board_sync.py +0 -208
  93. package/scripts/test_board_sync_create_idempotency.py +0 -117
  94. package/scripts/test_board_sync_wave_title.py +0 -120
  95. package/scripts/test_census_backstop.py +0 -712
  96. package/scripts/test_census_forward_contract.py +0 -91
  97. package/scripts/test_census_update_contract.test.mjs +0 -454
  98. package/scripts/test_codex_adapter_sync_contract.py +0 -169
  99. package/scripts/test_dist_kit_smoke.py +0 -220
  100. package/scripts/test_drift_guard_diagnostics.py +0 -295
  101. package/scripts/test_issue_claim_contract.py +0 -169
  102. package/scripts/test_kit_docs_language_census.py +0 -178
  103. package/scripts/test_marker_lib.py +0 -162
  104. package/scripts/test_orchestrate_wave_contract.py +0 -381
  105. package/scripts/test_pr_body_check.py +0 -264
  106. package/scripts/test_profile_globs.py +0 -280
  107. package/scripts/test_program_planning_contract.py +0 -133
  108. package/scripts/test_release_authorization_contract.py +0 -101
  109. package/scripts/test_render_anchor.py +0 -267
  110. package/scripts/test_retro_wrapup_contract.py +0 -153
  111. package/scripts/test_skill_code_review_seed.py +0 -89
  112. package/scripts/test_skill_codex_exec_lifecycle.py +0 -123
  113. package/scripts/test_skill_frontmatter_lint.py +0 -234
  114. package/scripts/test_skill_gh_lint.py +0 -124
  115. package/scripts/test_skill_language_census.py +0 -254
  116. package/scripts/test_skill_optional_readiness.py +0 -171
  117. package/scripts/test_skill_portability_lint.py +0 -866
  118. package/scripts/test_skill_precommit_template.py +0 -30
  119. package/scripts/test_skill_publish_audit.py +0 -358
  120. package/scripts/test_skill_readiness_contract.py +0 -264
  121. package/scripts/test_skill_readiness_preflight.py +0 -180
  122. package/scripts/test_skill_required_readiness.py +0 -233
  123. package/scripts/test_skill_selfcontainment_lint.py +0 -255
  124. package/scripts/test_skill_setup_workflow_seeds.py +0 -691
  125. package/scripts/test_skill_stale_name_lint.py +0 -126
  126. package/scripts/test_skill_surface_refs.py +0 -300
  127. package/scripts/test_skill_trailing_artifact_lint.py +0 -71
  128. package/scripts/test_tdd_contract.py +0 -78
  129. package/scripts/test_worktree_ignore_seed.py +0 -320
  130. package/scripts/test_worktree_setup_base_guard.py +0 -140
  131. package/scripts/test_worktree_wrapup_contract.py +0 -1648
  132. package/scripts/test_wrapup_land.py +0 -428
  133. package/scripts/worktree-lifecycle/session.py +0 -1857
@@ -1,91 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Forward proof: a clean public checkout builds and serves census consumers."""
3
- import json
4
- import shutil
5
- import subprocess
6
- import tempfile
7
- import unittest
8
- from pathlib import Path
9
-
10
- REPO = Path(__file__).resolve().parent.parent
11
- PUBLIC_UNIT_RELATIVE = Path("test/fixtures/census-consumers/public-unit.json")
12
- GREENFIELD_RELATIVE = Path("test/fixtures/census-consumers/greenfield")
13
-
14
-
15
- class CensusForwardContract(unittest.TestCase):
16
- def setUp(self):
17
- self.tmp = Path(tempfile.mkdtemp(prefix="awk-census-forward-"))
18
- self.addCleanup(shutil.rmtree, self.tmp, ignore_errors=True)
19
- self.public = self.tmp / "public-kit"
20
- self.public.mkdir()
21
- source_root = REPO
22
- self.forbidden_source_root = str(source_root)
23
- archive = subprocess.Popen(
24
- ["git", "archive", "HEAD"], cwd=source_root, stdout=subprocess.PIPE
25
- )
26
- unpack = subprocess.run(
27
- ["tar", "-x", "-C", str(self.public)], stdin=archive.stdout,
28
- capture_output=True, timeout=30,
29
- )
30
- archive.stdout.close()
31
- archive_rc = archive.wait(timeout=30)
32
- self.assertEqual((archive_rc, unpack.returncode), (0, 0), unpack.stderr.decode())
33
- # From this boundary onward every executable, fixture, and assertion input
34
- # is resolved inside the archived public checkout.
35
- self.assertTrue((self.public / GREENFIELD_RELATIVE).is_dir())
36
- self.assertTrue((self.public / PUBLIC_UNIT_RELATIVE).is_file())
37
- build = subprocess.run(
38
- ["node", "scripts/build-kit.mjs"], cwd=self.public,
39
- capture_output=True, text=True, timeout=120,
40
- )
41
- self.assertEqual(build.returncode, 0, build.stderr)
42
- self.dist = self.public / "dist-kit"
43
-
44
- def test_built_public_checkout_exercises_both_surfaces_and_scanner(self):
45
- claude = (self.dist / ".claude/skills/census-update/SKILL.md").read_text()
46
- codex = (self.dist / ".agents/skills/census-update/SKILL.md").read_text()
47
- self.assertEqual(claude, codex)
48
- for public_export in ("scanCensus", "diffCensus", "activateCensus"):
49
- self.assertIn(public_export, claude)
50
-
51
- consumer = self.tmp / "consumer"
52
- fixture = self.public / GREENFIELD_RELATIVE
53
- self.assertTrue(fixture.is_relative_to(self.public))
54
- shutil.copytree(fixture, consumer)
55
- subprocess.run(["git", "init", "--quiet"], cwd=consumer, check=True)
56
- subprocess.run(["git", "add", "."], cwd=consumer, check=True)
57
- runner = (
58
- "import { scanCensus } from "
59
- + json.dumps((self.dist / "scripts/census/index.mjs").as_uri())
60
- + "; const result = await scanCensus({repoRoot: process.argv[1], enabled:true});"
61
- + "console.log(JSON.stringify({state:result.state,count:result.denominator.length}));"
62
- )
63
- run = subprocess.run(
64
- ["node", "--input-type=module", "--eval", runner, str(consumer)],
65
- cwd=self.public, capture_output=True, text=True, timeout=30,
66
- )
67
- self.assertEqual(run.returncode, 0, run.stderr)
68
- self.assertEqual(json.loads(run.stdout), {"state": "bootstrap", "count": 2})
69
- self.assertNotIn("testreporter", run.stdout.lower())
70
- self.assertNotIn(self.forbidden_source_root, runner)
71
- self.assertNotIn(self.forbidden_source_root, run.stdout)
72
-
73
- def test_public_pack_contains_complete_census_unit_without_foreign_reach(self):
74
- pack = subprocess.run(
75
- ["npm", "pack", "--dry-run", "--json"], cwd=self.public,
76
- capture_output=True, text=True, timeout=120,
77
- )
78
- self.assertEqual(pack.returncode, 0, pack.stderr)
79
- paths = {entry["path"] for entry in json.loads(pack.stdout)[0]["files"]}
80
- expected = set(json.loads(
81
- (self.public / PUBLIC_UNIT_RELATIVE).read_text(encoding="utf-8")
82
- )["paths"])
83
- self.assertEqual(expected - paths, set())
84
- for path in expected:
85
- text = (self.public / path).read_text(encoding="utf-8")
86
- self.assertNotIn("tools/agent-workflow-kit", text)
87
- self.assertNotIn("testreporter", text.lower())
88
-
89
-
90
- if __name__ == "__main__":
91
- unittest.main(verbosity=2)
@@ -1,454 +0,0 @@
1
- import { test } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { execFile } from 'node:child_process';
4
- import { promisify } from 'node:util';
5
- import { access, lstat, mkdtemp, mkdir, readFile, realpath, rm, stat, symlink, writeFile } from 'node:fs/promises';
6
- import { tmpdir } from 'node:os';
7
- import { basename, isAbsolute, join, relative, resolve, sep } from 'node:path';
8
- import { pathToFileURL } from 'node:url';
9
- import {
10
- CENSUS_VERDICTS,
11
- activateCensus,
12
- diffCensus,
13
- fingerprintCensus,
14
- resolveCensusState,
15
- scanCensus,
16
- serializeCensus,
17
- } from './census/index.mjs';
18
-
19
- const ROOT = new URL('../', import.meta.url);
20
- const exec = promisify(execFile);
21
-
22
- async function text(path) {
23
- return readFile(new URL(path, ROOT), 'utf8');
24
- }
25
-
26
- async function makeBrownfield() {
27
- const root = await mkdtemp(join(tmpdir(), 'awk-census-update-'));
28
- await mkdir(join(root, 'apps/api/src'), { recursive: true });
29
- await mkdir(join(root, 'apps/web/src'), { recursive: true });
30
- await writeFile(join(root, 'package.json'), '{"name":"brownfield","type":"module"}\n');
31
- await writeFile(join(root, 'apps/api/src/index.mjs'), 'export const api = true;\n');
32
- await writeFile(join(root, 'apps/web/src/index.mjs'), 'export const web = true;\n');
33
- await exec('git', ['init', '--quiet'], { cwd: root });
34
- await exec('git', ['add', '.'], { cwd: root });
35
- return root;
36
- }
37
-
38
- const PROFILE_RELATIVE_PATH = '.census/profile.json';
39
- const ACTIVE_RELATIVE_PATH = '.census/active.json';
40
-
41
- async function readJson(path) {
42
- return JSON.parse(await readFile(path, 'utf8'));
43
- }
44
-
45
- function profileBehaviorFamilies(profile) {
46
- return profile.decisions.map(({ family, status }) => ({ name: family, status }));
47
- }
48
-
49
- async function containedRegularPath(root, localPath) {
50
- if (typeof localPath !== 'string' || !localPath || isAbsolute(localPath)) return null;
51
- const target = resolve(root, localPath);
52
- const lexical = relative(resolve(root), target);
53
- if (lexical === '..' || lexical.startsWith(`..${sep}`) || isAbsolute(lexical)) return null;
54
- const stats = await lstat(target);
55
- if (!stats.isFile() || stats.isSymbolicLink()) return null;
56
- const canonicalRoot = await realpath(root);
57
- const canonical = await realpath(target);
58
- const canonicalRelative = relative(canonicalRoot, canonical);
59
- if (canonicalRelative === '..' || canonicalRelative.startsWith(`..${sep}`)
60
- || isAbsolute(canonicalRelative)) return null;
61
- return target;
62
- }
63
-
64
- async function runLocalScannerProof(root, record) {
65
- const modulePath = await containedRegularPath(root, record.module);
66
- const testPath = await containedRegularPath(root, record.test);
67
- if (!modulePath || !testPath) return false;
68
- await exec('node', ['--test', record.test], { cwd: root });
69
- const scanner = await import(`${pathToFileURL(modulePath).href}?proof=${Date.now()}`);
70
- return scanner[record.export]().includes(record.surface);
71
- }
72
-
73
- async function runConsumerCensus(root) {
74
- const profilePath = join(root, PROFILE_RELATIVE_PATH);
75
- const activePath = join(root, ACTIVE_RELATIVE_PATH);
76
- const profile = await readJson(profilePath);
77
- let previous = null;
78
- try {
79
- previous = await readJson(activePath);
80
- } catch (error) {
81
- if (error.code !== 'ENOENT') throw error;
82
- }
83
-
84
- const scan = await scanCensus({
85
- repoRoot: root,
86
- enabled: profile.enabled,
87
- hasActive: previous !== null,
88
- behaviorFamilies: profileBehaviorFamilies(profile),
89
- });
90
- const previousSurfaces = new Set(previous?.families.surfaces.map(({ name }) => name) ?? []);
91
- const unexpected = previous === null
92
- ? []
93
- : scan.families.surfaces.filter(({ name }) => !previousSurfaces.has(name));
94
- const scannerBySurface = new Map(profile.localScanners.map((record) => [record.surface, record]));
95
- const missingScanner = unexpected.some(({ name }) => !scannerBySurface.has(name));
96
- const hasOpen = missingScanner || [...scan.families.surfaces, ...scan.families.behaviors]
97
- .some(({ status }) => status === CENSUS_VERDICTS.open);
98
- const candidate = {
99
- ...scan,
100
- profileReport: {
101
- decisions: profile.decisions,
102
- localScanners: profile.localScanners,
103
- overrides: profile.overrides,
104
- },
105
- state: resolveCensusState({ enabled: profile.enabled, hasActive: true, hasOpen }),
106
- };
107
-
108
- if (previous !== null && serializeCensus(candidate) === serializeCensus(previous)) {
109
- return { activated: false, activePath, candidate, profilePath };
110
- }
111
-
112
- await activateCensus({
113
- activePath,
114
- candidate,
115
- verify: async (staged) => {
116
- if (staged.state !== 'current') return false;
117
- for (const { name } of unexpected) {
118
- const record = scannerBySurface.get(name);
119
- if (!record || !await runLocalScannerProof(root, record)) return false;
120
- }
121
- return staged.profileReport.decisions.every((decision) => (
122
- decision.status !== CENSUS_VERDICTS.notRelevant || Boolean(decision.justification)
123
- ));
124
- },
125
- });
126
- return { activated: true, activePath, candidate, profilePath };
127
- }
128
-
129
- test('census-update is a published dual-surface own-work entry point', async () => {
130
- const [source, mirror, manifestText, provenance, claudeOverview, codexOverview] = await Promise.all([
131
- text('.claude/skills/census-update/SKILL.md'),
132
- text('.agents/skills/census-update/SKILL.md'),
133
- text('.claude/skills/skill-manifest.json'),
134
- text('PROVENANCE.md'),
135
- text('.claude/skills/setup-workflow/workflow-overview.md'),
136
- text('.agents/skills/setup-workflow/workflow-overview.md'),
137
- ]);
138
- const entry = JSON.parse(manifestText).skills['census-update'];
139
-
140
- assert.deepEqual(entry, {
141
- class: 'generic',
142
- publish: true,
143
- entryPoint: true,
144
- surfaces: ['claude', 'codex'],
145
- provenance: 'own',
146
- });
147
- assert.equal(source, mirror);
148
- assert.match(provenance, /Own work[\s\S]*census-update/);
149
- assert.match(claudeOverview, /`census-update`/);
150
- assert.match(codexOverview, /`census-update`/);
151
- });
152
-
153
- test('census-update coordinates the stable foundation API without a second engine', async () => {
154
- const skill = await text('.claude/skills/census-update/SKILL.md');
155
- for (const name of [
156
- 'scanCensus', 'serializeCensus', 'fingerprintCensus', 'CENSUS_BUILDER_VERSION',
157
- 'diffCensus', 'CENSUS_STATES', 'CENSUS_VERDICTS', 'resolveCensusState',
158
- 'activateCensus', 'CensusTransactionError',
159
- ]) {
160
- assert.match(skill, new RegExp(`\\b${name}\\b`), `${name} must remain the public mechanism`);
161
- }
162
- assert.match(skill, /do not copy, rename, or reimplement/i);
163
- assert.match(skill, /Never ask the user which files, paths, or\s+patterns exist/i);
164
- assert.match(skill, /recommendation and the evidence/i);
165
- assert.match(skill, /`nicht relevant` requires a durable justification/i);
166
- assert.match(skill, /override[\s\S]*must not[\s\S]*alter scanner facts/i);
167
- assert.match(skill, /Work only in the current repository/i);
168
- assert.match(skill, /Never[\s\S]*propose an upstream kit change/i);
169
- assert.match(skill, /\.census\/profile\.json/);
170
- assert.match(skill, /\.census\/active\.json/);
171
- assert.match(skill, /schemaVersion/);
172
- assert.match(skill, /every `localScanners\[\]\.test`/);
173
- assert.match(skill, /shared project-local census check entry point/i);
174
- assert.match(
175
- skill,
176
- /It must execute every declared focused test,\s+and both local CI and pre-push must transitively reach that same entry point\./i,
177
- );
178
- assert.match(skill, /executable wiring test/i);
179
- assert.match(skill, /missing or partial[\s\S]*failed/i);
180
- assert.match(skill, /previous active\s+census bytes are unchanged/i);
181
- assert.match(skill, /idempotent/i);
182
-
183
- const setupCensus = await text('.claude/skills/setup-workflow/census.md');
184
- assert.match(setupCensus, /activation[\s\S]*durable[\s\S]*local CI[\s\S]*pre-push/i);
185
- assert.match(setupCensus, /remove only[\s\S]*kit-owned census wiring/i);
186
- });
187
-
188
- test('brownfield bootstrap activates real surface coverage with a separate behavior overview', async () => {
189
- const root = await makeBrownfield();
190
- const profilePath = join(root, PROFILE_RELATIVE_PATH);
191
- try {
192
- await assert.rejects(access(profilePath), (error) => error.code === 'ENOENT');
193
- const facts = await scanCensus({ repoRoot: root, enabled: true });
194
- assert.equal(facts.state, 'bootstrap');
195
- await mkdir(join(root, '.census'), { recursive: true });
196
- await writeFile(profilePath, `${JSON.stringify({
197
- schemaVersion: 1,
198
- enabled: true,
199
- decisions: [
200
- { family: 'authentication', status: CENSUS_VERDICTS.covered, evidence: 'apps/api/src' },
201
- {
202
- family: 'email-delivery',
203
- status: CENSUS_VERDICTS.notRelevant,
204
- justification: 'The product sends no email.',
205
- },
206
- ],
207
- localScanners: [],
208
- overrides: [],
209
- })}\n`);
210
-
211
- const { activePath, candidate } = await runConsumerCensus(root);
212
- const persisted = await readJson(activePath);
213
- const covered = persisted.families.surfaces
214
- .filter(({ status }) => status === CENSUS_VERDICTS.covered).length;
215
-
216
- assert.equal(candidate.state, 'current');
217
- assert.equal(persisted.state, 'current');
218
- assert.equal(`${covered} of ${persisted.families.surfaces.length}`, '3 of 3');
219
- assert.deepEqual(persisted.families.behaviors, [
220
- { name: 'authentication', status: 'abgedeckt', type: 'behavior' },
221
- { name: 'email-delivery', status: 'nicht relevant', type: 'behavior' },
222
- ]);
223
- assert.equal(persisted.profileReport.decisions[1].justification, 'The product sends no email.');
224
- } finally {
225
- await rm(root, { recursive: true, force: true });
226
- }
227
- });
228
-
229
- test('a second unchanged current run performs no activation write', async () => {
230
- const root = await makeBrownfield();
231
- try {
232
- await mkdir(join(root, '.census'), { recursive: true });
233
- await writeFile(join(root, PROFILE_RELATIVE_PATH), `${JSON.stringify({
234
- schemaVersion: 1,
235
- enabled: true,
236
- decisions: [],
237
- localScanners: [],
238
- overrides: [],
239
- })}\n`);
240
- const first = await runConsumerCensus(root);
241
- assert.equal(first.activated, true);
242
- const { activePath } = first;
243
- const beforeBytes = await readFile(activePath, 'utf8');
244
- const beforeStat = await stat(activePath);
245
-
246
- const second = await runConsumerCensus(root);
247
-
248
- assert.equal(second.activated, false);
249
- assert.equal(await readFile(activePath, 'utf8'), beforeBytes);
250
- assert.equal((await stat(activePath)).mtimeMs, beforeStat.mtimeMs);
251
- } finally {
252
- await rm(root, { recursive: true, force: true });
253
- }
254
- });
255
-
256
- test('an unexpected surface stays open despite not-relevant decisions and a change-local override', async () => {
257
- const root = await makeBrownfield();
258
- try {
259
- await mkdir(join(root, '.census'), { recursive: true });
260
- await writeFile(join(root, PROFILE_RELATIVE_PATH), `${JSON.stringify({
261
- schemaVersion: 1,
262
- enabled: true,
263
- decisions: [{
264
- family: 'postal-delivery',
265
- justification: 'The product sends no physical mail.',
266
- status: CENSUS_VERDICTS.notRelevant,
267
- }],
268
- localScanners: [],
269
- overrides: [{ reason: 'Known formatting-only path delta.', scope: 'this change' }],
270
- })}\n`);
271
- await runConsumerCensus(root);
272
- await mkdir(join(root, 'services/payments/src'), { recursive: true });
273
- await writeFile(join(root, 'services/payments/src/index.mjs'), 'export const payments = true;\n');
274
- await exec('git', ['add', 'services'], { cwd: root });
275
- const before = await readJson(join(root, ACTIVE_RELATIVE_PATH));
276
-
277
- await assert.rejects(runConsumerCensus(root), (error) => error.state === 'failed');
278
- const after = await readJson(join(root, ACTIVE_RELATIVE_PATH));
279
- assert.equal(serializeCensus(after), serializeCensus(before));
280
- assert.equal(after.profileReport.decisions[0].status, 'nicht relevant');
281
- assert.equal(after.profileReport.decisions[0].justification, 'The product sends no physical mail.');
282
- assert.equal(after.profileReport.overrides[0].scope, 'this change');
283
- } finally {
284
- await rm(root, { recursive: true, force: true });
285
- }
286
- });
287
-
288
- test('a changed repository yields only compact path deltas and never reads a foreign repository', async () => {
289
- const root = await makeBrownfield();
290
- const foreign = await makeBrownfield();
291
- try {
292
- const reads = [];
293
- const before = await scanCensus({ repoRoot: root });
294
- await writeFile(join(root, 'apps/api/src/index.mjs'), 'export const api = "changed";\n');
295
- const after = await scanCensus({
296
- repoRoot: root,
297
- readText: async (path) => {
298
- reads.push(path);
299
- return readFile(path, 'utf8');
300
- },
301
- });
302
-
303
- assert.deepEqual(diffCensus(before, after), {
304
- added: [],
305
- changed: ['apps/api/src/index.mjs'],
306
- open: [],
307
- removed: [],
308
- });
309
- assert.ok(reads.length > 0);
310
- assert.ok(reads.every((path) => path.startsWith(`${root}/`)));
311
- assert.ok(reads.every((path) => !path.startsWith(`${foreign}/`)));
312
- } finally {
313
- await rm(root, { recursive: true, force: true });
314
- await rm(foreign, { recursive: true, force: true });
315
- }
316
- });
317
-
318
- test('an injected activation failure byte-preserves the active census', async () => {
319
- const root = await makeBrownfield();
320
- const activePath = join(root, '.census/active.json');
321
- const previous = '{"generation":"known-good"}\n';
322
- try {
323
- await mkdir(join(root, '.census'), { recursive: true });
324
- await writeFile(activePath, previous);
325
- const candidate = await scanCensus({ repoRoot: root, enabled: true, hasActive: true });
326
-
327
- await assert.rejects(
328
- activateCensus({
329
- activePath,
330
- candidate,
331
- verify: async () => true,
332
- renameCandidate: async () => { throw new Error('injected swap failure'); },
333
- }),
334
- (error) => error.state === 'failed',
335
- );
336
- assert.equal(await readFile(activePath, 'utf8'), previous);
337
- } finally {
338
- await rm(root, { recursive: true, force: true });
339
- }
340
- });
341
-
342
- test('an unknown pattern activates only after a repository-local scanner test passes', async () => {
343
- const root = await makeBrownfield();
344
- try {
345
- await mkdir(join(root, '.census'), { recursive: true });
346
- const profilePath = join(root, PROFILE_RELATIVE_PATH);
347
- await writeFile(profilePath, `${JSON.stringify({
348
- schemaVersion: 1,
349
- enabled: true,
350
- decisions: [],
351
- localScanners: [],
352
- overrides: [],
353
- })}\n`);
354
- const { activePath } = await runConsumerCensus(root);
355
- const previous = await readFile(activePath, 'utf8');
356
- await mkdir(join(root, 'services/payments/src'), { recursive: true });
357
- await writeFile(join(root, 'services/payments/src/index.mjs'), 'export const payments = true;\n');
358
- await exec('git', ['add', 'services'], { cwd: root });
359
-
360
- await assert.rejects(runConsumerCensus(root), (error) => error.state === 'failed');
361
- assert.equal(await readFile(activePath, 'utf8'), previous);
362
-
363
- await mkdir(join(root, 'scripts/census-local'), { recursive: true });
364
- await mkdir(join(root, 'test'), { recursive: true });
365
- await writeFile(
366
- join(root, 'scripts/census-local/scan-services.mjs'),
367
- "export const scanServices = () => ['services/payments/src'];\n",
368
- );
369
- await writeFile(
370
- join(root, 'test/census-services.test.mjs'),
371
- "import { test } from 'node:test';\n"
372
- + "import assert from 'node:assert/strict';\n"
373
- + "import { scanServices } from '../scripts/census-local/scan-services.mjs';\n"
374
- + "test('service scanner recognizes payments', () => {\n"
375
- + " assert.deepEqual(scanServices(), ['services/payments/src']);\n"
376
- + "});\n",
377
- );
378
- await exec('git', ['add', 'services', 'scripts/census-local', 'test/census-services.test.mjs'], { cwd: root });
379
- const withScanner = await readJson(profilePath);
380
- withScanner.localScanners.push({
381
- surface: 'services/payments/src',
382
- module: 'scripts/census-local/scan-services.mjs',
383
- export: 'scanServices',
384
- test: 'test/census-services.test.mjs',
385
- });
386
- await writeFile(profilePath, `${JSON.stringify(withScanner)}\n`);
387
-
388
- const { candidate } = await runConsumerCensus(root);
389
- const activated = await readJson(activePath);
390
- assert.equal(candidate.state, 'current');
391
- assert.equal(activated.profileReport.localScanners[0].test, 'test/census-services.test.mjs');
392
- assert.notEqual(await readFile(activePath, 'utf8'), previous);
393
-
394
- const fingerprintBeforeOverride = fingerprintCensus(activated);
395
- withScanner.overrides.push({ reason: 'display only', scope: 'this change' });
396
- await writeFile(profilePath, `${JSON.stringify(withScanner)}\n`);
397
- const raw = await scanCensus({ repoRoot: root, enabled: true, hasActive: true });
398
- assert.deepEqual(fingerprintCensus(raw), fingerprintBeforeOverride);
399
- } finally {
400
- await rm(root, { recursive: true, force: true });
401
- }
402
- });
403
-
404
- test('local scanner proof rejects absolute, escaping, and symlinked paths', async () => {
405
- const root = await makeBrownfield();
406
- const foreign = await mkdtemp(join(tmpdir(), 'awk-foreign-census-proof-'));
407
- try {
408
- await mkdir(join(root, '.census'), { recursive: true });
409
- const profilePath = join(root, PROFILE_RELATIVE_PATH);
410
- const profile = {
411
- schemaVersion: 1,
412
- enabled: true,
413
- decisions: [],
414
- localScanners: [],
415
- overrides: [],
416
- };
417
- await writeFile(profilePath, `${JSON.stringify(profile)}\n`);
418
- const { activePath } = await runConsumerCensus(root);
419
- const previous = await readFile(activePath, 'utf8');
420
- await mkdir(join(root, 'services/payments/src'), { recursive: true });
421
- await writeFile(join(root, 'services/payments/src/index.mjs'), 'export const payments = true;\n');
422
- await exec('git', ['add', 'services'], { cwd: root });
423
-
424
- await writeFile(join(foreign, 'scanner.mjs'), "export const scanServices = () => ['services/payments/src'];\n");
425
- await writeFile(
426
- join(foreign, 'scanner.test.mjs'),
427
- "import { test } from 'node:test'; test('foreign proof', () => {});\n",
428
- );
429
- const records = [
430
- { module: join(foreign, 'scanner.mjs'), test: join(foreign, 'scanner.test.mjs') },
431
- {
432
- module: `../${basename(foreign)}/scanner.mjs`,
433
- test: `../${basename(foreign)}/scanner.test.mjs`,
434
- },
435
- ];
436
- await symlink(join(foreign, 'scanner.mjs'), join(root, 'scanner-link.mjs'));
437
- await symlink(join(foreign, 'scanner.test.mjs'), join(root, 'scanner-link.test.mjs'));
438
- records.push({ module: 'scanner-link.mjs', test: 'scanner-link.test.mjs' });
439
-
440
- for (const record of records) {
441
- profile.localScanners = [{
442
- surface: 'services/payments/src',
443
- export: 'scanServices',
444
- ...record,
445
- }];
446
- await writeFile(profilePath, `${JSON.stringify(profile)}\n`);
447
- await assert.rejects(runConsumerCensus(root), (error) => error.state === 'failed');
448
- assert.equal(await readFile(activePath, 'utf8'), previous);
449
- }
450
- } finally {
451
- await rm(root, { recursive: true, force: true });
452
- await rm(foreign, { recursive: true, force: true });
453
- }
454
- });