@ikon85/agent-workflow-kit 0.43.0 → 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 (126) 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 +249 -7
  24. package/agent-workflow-kit.package.json +42 -42
  25. package/package.json +16 -12
  26. package/scripts/marker_lib.py +3 -0
  27. package/scripts/profile_globs.py +26 -45
  28. package/scripts/release-delta-guard.mjs +26 -9
  29. package/scripts/worktree-lifecycle/README.md +57 -160
  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 -1429
  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/workflow-capabilities.json +0 -28
  53. package/docs/research/agent-task-taxonomy-benchmark-coverage.md +0 -451
  54. package/docs/research/benchlm-routing-source.md +0 -198
  55. package/docs/research/consumer-owned-protocol-files.md +0 -238
  56. package/docs/research/frontend-agent-benchmarks.md +0 -282
  57. package/docs/research/model-effort-routing-benchmarks.md +0 -261
  58. package/docs/research/provider-neutral-agent-routing.md +0 -207
  59. package/docs/research/wave-152-consumer-acceptance.md +0 -98
  60. package/docs/research/wave-43-script-hook-census.md +0 -128
  61. package/scripts/build-kit.mjs +0 -117
  62. package/scripts/build-kit.test.mjs +0 -207
  63. package/scripts/census/census.test.mjs +0 -252
  64. package/scripts/census/state.test.mjs +0 -15
  65. package/scripts/census/transaction.test.mjs +0 -103
  66. package/scripts/census-contract.test.mjs +0 -227
  67. package/scripts/check-kit-staleness.mjs +0 -45
  68. package/scripts/check-kit-staleness.test.mjs +0 -19
  69. package/scripts/codex-exec-scenarios/fake-codex.mjs +0 -165
  70. package/scripts/codex-exec.test.mjs +0 -871
  71. package/scripts/grill-census-wiring-guard.mjs +0 -87
  72. package/scripts/grill-census-wiring-guard.test.mjs +0 -107
  73. package/scripts/kit-release.test.mjs +0 -273
  74. package/scripts/kit-update-pr.test.mjs +0 -151
  75. package/scripts/lib/audit-refs.mjs +0 -146
  76. package/scripts/lib/scrub.mjs +0 -113
  77. package/scripts/lib/scrub.test.mjs +0 -187
  78. package/scripts/memory-lifecycle/memory-lifecycle.test.mjs +0 -170
  79. package/scripts/portability_profile_scan.py +0 -53
  80. package/scripts/release-delta-guard.test.mjs +0 -174
  81. package/scripts/release-parity.test.mjs +0 -90
  82. package/scripts/release-state.test.mjs +0 -360
  83. package/scripts/test_anchor_table.py +0 -69
  84. package/scripts/test_board_bootstrap.py +0 -348
  85. package/scripts/test_board_sync.py +0 -208
  86. package/scripts/test_board_sync_create_idempotency.py +0 -117
  87. package/scripts/test_board_sync_wave_title.py +0 -120
  88. package/scripts/test_census_backstop.py +0 -712
  89. package/scripts/test_census_forward_contract.py +0 -91
  90. package/scripts/test_census_update_contract.test.mjs +0 -454
  91. package/scripts/test_codex_adapter_sync_contract.py +0 -169
  92. package/scripts/test_dist_kit_smoke.py +0 -220
  93. package/scripts/test_drift_guard_diagnostics.py +0 -295
  94. package/scripts/test_issue_claim_contract.py +0 -169
  95. package/scripts/test_kit_docs_language_census.py +0 -178
  96. package/scripts/test_marker_lib.py +0 -162
  97. package/scripts/test_orchestrate_wave_contract.py +0 -381
  98. package/scripts/test_pr_body_check.py +0 -264
  99. package/scripts/test_profile_globs.py +0 -280
  100. package/scripts/test_program_planning_contract.py +0 -133
  101. package/scripts/test_release_authorization_contract.py +0 -101
  102. package/scripts/test_render_anchor.py +0 -267
  103. package/scripts/test_retro_wrapup_contract.py +0 -153
  104. package/scripts/test_skill_code_review_seed.py +0 -89
  105. package/scripts/test_skill_codex_exec_lifecycle.py +0 -123
  106. package/scripts/test_skill_frontmatter_lint.py +0 -234
  107. package/scripts/test_skill_gh_lint.py +0 -124
  108. package/scripts/test_skill_language_census.py +0 -254
  109. package/scripts/test_skill_optional_readiness.py +0 -171
  110. package/scripts/test_skill_portability_lint.py +0 -866
  111. package/scripts/test_skill_precommit_template.py +0 -30
  112. package/scripts/test_skill_publish_audit.py +0 -358
  113. package/scripts/test_skill_readiness_contract.py +0 -264
  114. package/scripts/test_skill_readiness_preflight.py +0 -180
  115. package/scripts/test_skill_required_readiness.py +0 -233
  116. package/scripts/test_skill_selfcontainment_lint.py +0 -255
  117. package/scripts/test_skill_setup_workflow_seeds.py +0 -691
  118. package/scripts/test_skill_stale_name_lint.py +0 -126
  119. package/scripts/test_skill_surface_refs.py +0 -300
  120. package/scripts/test_skill_trailing_artifact_lint.py +0 -71
  121. package/scripts/test_tdd_contract.py +0 -78
  122. package/scripts/test_worktree_ignore_seed.py +0 -320
  123. package/scripts/test_worktree_setup_base_guard.py +0 -140
  124. package/scripts/test_worktree_wrapup_contract.py +0 -1737
  125. package/scripts/test_wrapup_land.py +0 -428
  126. package/scripts/worktree-lifecycle/session.py +0 -1857
@@ -1,207 +0,0 @@
1
- import { test } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { access, mkdir, mkdtemp, readFile, rm, stat, writeFile } from 'node:fs/promises';
4
- import { tmpdir } from 'node:os';
5
- import { join, dirname } from 'node:path';
6
- import { fileURLToPath } from 'node:url';
7
- import { createHash } from 'node:crypto';
8
- import { execFileSync } from 'node:child_process';
9
- import { buildKit } from './build-kit.mjs';
10
- import { init } from '../src/commands/init.mjs';
11
- import { HELPER_FILES, STUB_TARGETS } from '../src/lib/bundle.mjs';
12
- import { CONSUMER_MANIFEST_NAME, readManifest } from '../src/lib/manifest.mjs';
13
-
14
- const REPO = join(dirname(fileURLToPath(import.meta.url)), '..');
15
- const PUBLIC_CENSUS_UNIT = JSON.parse(await readFile(
16
- join(REPO, 'test/fixtures/census-consumers/public-unit.json'), 'utf8',
17
- )).paths;
18
- const WAVE_152_HELPERS = [
19
- { path: 'scripts/marker_lib.py', kind: 'script', mode: 0o644 },
20
- { path: 'scripts/find-by-marker.py', kind: 'script', mode: 0o755 },
21
- { path: 'scripts/codex-exec.sh', kind: 'script', mode: 0o755 },
22
- { path: 'scripts/codex_proc.py', kind: 'script', mode: 0o644 },
23
- { path: 'scripts/render-anchor.py', kind: 'script', mode: 0o755 },
24
- ];
25
- async function withBuild(fn) {
26
- const dist = await mkdtemp(join(tmpdir(), 'awkit-build-'));
27
- try { return await fn(dist, await buildKit({ repoRoot: REPO, distDir: dist })); }
28
- finally { await rm(dist, { recursive: true, force: true }); }
29
- }
30
- const exists = (path) => access(path).then(() => true, () => false);
31
-
32
- test('historical v0.9.0 manifest remains an immutable golden fixture', async () => {
33
- const fixture = await readFile(join(REPO, 'test/fixtures/v0.9.0-agent-workflow-kit.package.json'));
34
- assert.equal(createHash('sha256').update(fixture).digest('hex'),
35
- 'a209e14b1a2e5b3bba63c16ee6a7d713b6acd9a1d11379bb07fdd1e3e4ca8b6c');
36
- const manifest = JSON.parse(fixture);
37
- assert.equal(manifest.kitVersion, '0.9.0');
38
- assert.equal(manifest.files.length, 215);
39
- });
40
-
41
- test('current public SSOT builds deterministically', async () => {
42
- const first = await withBuild(async (dist) => readFile(join(dist, 'agent-workflow-kit.package.json'), 'utf8'));
43
- const second = await withBuild(async (dist) => readFile(join(dist, 'agent-workflow-kit.package.json'), 'utf8'));
44
- assert.equal(first, second);
45
- });
46
-
47
- test('current build assigns every file an explicit install role', async () => {
48
- await withBuild(async (dist) => {
49
- const manifest = JSON.parse(await readFile(join(dist, 'agent-workflow-kit.package.json'), 'utf8'));
50
- assert.ok(manifest.files.every(({ installRole }) => ['consumer', 'maintainer'].includes(installRole)));
51
- assert.deepEqual(
52
- manifest.files.filter(({ installRole }) => installRole === 'maintainer').map(({ path }) => path),
53
- [
54
- '.agents/skills/kit-release/SKILL.md',
55
- '.claude/skills/kit-release/SKILL.md',
56
- 'scripts/kit-release.mjs',
57
- 'scripts/release-delta-guard.mjs',
58
- ],
59
- );
60
- });
61
- });
62
-
63
- test('a built kit installs no maintainer-only release surface into a consumer', async () => {
64
- await withBuild(async (dist) => {
65
- const consumer = await mkdtemp(join(tmpdir(), 'awkit-role-consumer-'));
66
- try {
67
- await init({ kitRoot: dist, consumerRoot: consumer });
68
- const maintainerPaths = [
69
- '.agents/skills/kit-release/SKILL.md',
70
- '.claude/skills/kit-release/SKILL.md',
71
- 'scripts/kit-release.mjs',
72
- 'scripts/release-delta-guard.mjs',
73
- ];
74
- assert.deepEqual(
75
- await Promise.all(maintainerPaths.map((path) => exists(join(consumer, path)))),
76
- [false, false, false, false],
77
- );
78
- const manifest = await readManifest(join(consumer, CONSUMER_MANIFEST_NAME));
79
- assert.equal(manifest.installRole, 'consumer');
80
- assert.ok(manifest.installed.every(({ installRole }) => installRole === 'consumer'));
81
- } finally {
82
- await rm(consumer, { recursive: true, force: true });
83
- }
84
- });
85
- });
86
-
87
- test('current build contains post-tag public files and repository metadata', async () => {
88
- await withBuild(async (dist) => {
89
- await readFile(join(dist, '.agents/skills/codex-adapter-sync/SKILL.md'));
90
- await readFile(join(dist, 'scripts/program_graph.py'));
91
- const pkg = JSON.parse(await readFile(join(dist, 'package.json'), 'utf8'));
92
- assert.equal(pkg.name, '@ikon85/agent-workflow-kit');
93
- assert.equal(pkg.repository.url, 'git+https://github.com/iKon85/agent-workflow-kit.git');
94
- assert.equal(pkg.bin['agent-workflow-kit'], 'src/cli.mjs');
95
- assert.equal(pkg.bin['agent-workflow-kit-update-pr'], 'scripts/kit-update-pr.mjs');
96
- await readFile(join(dist, 'scripts/kit-update-pr.mjs'));
97
- });
98
- });
99
-
100
- test('Wave 152 helpers keep their bundle and built-manifest modes', async () => {
101
- assert.deepEqual(
102
- WAVE_152_HELPERS.map(({ path }) => HELPER_FILES.find((entry) => entry.path === path)),
103
- WAVE_152_HELPERS,
104
- );
105
- await withBuild(async (dist) => {
106
- const manifest = JSON.parse(await readFile(join(dist, 'agent-workflow-kit.package.json'), 'utf8'));
107
- const byPath = new Map(manifest.files.map((entry) => [entry.path, entry]));
108
- assert.deepEqual(
109
- WAVE_152_HELPERS.map(({ path }) => {
110
- const { kind, mode, installRole } = byPath.get(path);
111
- return { path, kind, mode, installRole };
112
- }),
113
- WAVE_152_HELPERS.map((entry) => ({ ...entry, installRole: 'consumer' })),
114
- );
115
- });
116
- });
117
-
118
- test('current build contains the complete dual-surface census consumer unit', async () => {
119
- await withBuild(async (dist, report) => {
120
- const manifest = JSON.parse(await readFile(join(dist, 'agent-workflow-kit.package.json'), 'utf8'));
121
- const paths = new Set(manifest.files.map(({ path }) => path));
122
- assert.deepEqual(PUBLIC_CENSUS_UNIT.filter((path) => !paths.has(path)), []);
123
- assert.equal(report.fileCount, manifest.files.length);
124
- assert.equal(
125
- await readFile(join(dist, '.claude/skills/census-update/SKILL.md'), 'utf8'),
126
- await readFile(join(dist, '.agents/skills/census-update/SKILL.md'), 'utf8'),
127
- );
128
- });
129
- });
130
-
131
- test('current build is self-contained and never reaches into a consumer checkout', async () => {
132
- const source = await readFile(join(REPO, 'scripts/build-kit.mjs'), 'utf8');
133
- assert.doesNotMatch(source, /testreporter|tools\/agent-workflow-kit/);
134
- });
135
-
136
- test('npm pack keeps product files but excludes runtime residue', async () => {
137
- const logsDir = join(REPO, '.claude/logs');
138
- await mkdir(logsDir, { recursive: true });
139
- const ownedLogDir = await mkdtemp(join(logsDir, 'pack-runtime-'));
140
- const runtimeLog = join(ownedLogDir, 'drift-guard.log');
141
- const sentinelLog = join(ownedLogDir, 'preexisting.log');
142
- const sentinelBytes = 'preexisting runtime log bytes\n';
143
- await writeFile(sentinelLog, sentinelBytes);
144
- await writeFile(runtimeLog, `${new Date().toISOString()} runtime-only\n`);
145
- try {
146
- const output = execFileSync('npm', ['pack', '--dry-run', '--json'], {
147
- cwd: REPO, encoding: 'utf8',
148
- });
149
- const files = JSON.parse(output)[0].files.map((file) => file.path);
150
- assert.ok(files.includes('scripts/build-kit.mjs'));
151
- assert.ok(files.includes('scripts/board-sync.py'));
152
- assert.ok(files.includes('scripts/kit-update-pr.mjs'));
153
- assert.ok(files.includes('.claude/hooks/drift-guard.py'));
154
- assert.ok(files.includes('.claude/skills/tdd/SKILL.md'));
155
- for (const path of PUBLIC_CENSUS_UNIT) {
156
- assert.ok(files.includes(path), `pack missing ${path}`);
157
- }
158
- for (const path of STUB_TARGETS) {
159
- assert.ok(!files.includes(path), `pack must exclude init stub target ${path}`);
160
- }
161
- const packedByPath = new Map(JSON.parse(output)[0].files.map((file) => [file.path, file]));
162
- assert.deepEqual(
163
- WAVE_152_HELPERS.map(({ path, mode }) => ({ path, mode: packedByPath.get(path)?.mode })),
164
- WAVE_152_HELPERS.map(({ path, mode }) => ({ path, mode })),
165
- );
166
- assert.ok(files.every((path) => !path.startsWith('.claude/logs/')));
167
- assert.ok(files.every((path) => !path.includes('__pycache__') && !path.endsWith('.pyc')));
168
- const pkg = JSON.parse(execFileSync('node', ['-p', 'JSON.stringify(require("./package.json"))'], {
169
- cwd: REPO, encoding: 'utf8',
170
- }));
171
- assert.ok(pkg.files.includes('scripts/'), 'package must ship future script file types');
172
- assert.equal(await readFile(sentinelLog, 'utf8'), sentinelBytes);
173
- } finally {
174
- await rm(ownedLogDir, { recursive: true, force: true });
175
- }
176
- });
177
-
178
- test('packed scoped artifact keeps the existing npx default-bin inference', async () => {
179
- const destination = await mkdtemp(join(tmpdir(), 'awkit-pack-'));
180
- try {
181
- const packed = JSON.parse(execFileSync('npm', [
182
- 'pack', '--pack-destination', destination, '--json',
183
- ], { cwd: REPO, encoding: 'utf8' }));
184
- const packedByPath = new Map(packed[0].files.map((file) => [file.path, file]));
185
- execFileSync('tar', ['-xzf', join(destination, packed[0].filename), '-C', destination]);
186
- for (const { path, mode } of WAVE_152_HELPERS) {
187
- assert.equal(packedByPath.get(path)?.mode, mode, `packed mode mismatch: ${path}`);
188
- assert.equal(
189
- (await stat(join(destination, 'package', path))).mode & 0o777,
190
- mode,
191
- `extracted mode mismatch: ${path}`,
192
- );
193
- }
194
- const output = execFileSync('npx', [
195
- '--yes', '--offline', `./${packed[0].filename}`,
196
- ], { cwd: destination, encoding: 'utf8' });
197
- assert.match(output, /Usage: agent-workflow-kit/);
198
-
199
- const updater = execFileSync('npm', [
200
- 'exec', '--yes', '--offline', `--package=./${packed[0].filename}`,
201
- '--', 'agent-workflow-kit-update-pr', 'help',
202
- ], { cwd: destination, encoding: 'utf8' });
203
- assert.match(updater, /Usage: agent-workflow-kit-update-pr/);
204
- } finally {
205
- await rm(destination, { recursive: true, force: true });
206
- }
207
- });
@@ -1,252 +0,0 @@
1
- import { test } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { cp, mkdtemp, mkdir, rm, symlink, writeFile } from 'node:fs/promises';
4
- import { execFile } from 'node:child_process';
5
- import { promisify } from 'node:util';
6
- import { tmpdir } from 'node:os';
7
- import { join } from 'node:path';
8
- import {
9
- CENSUS_VERDICTS,
10
- diffCensus,
11
- scanCensus,
12
- serializeCensus,
13
- } from './index.mjs';
14
-
15
- const exec = promisify(execFile);
16
- const FIXTURE = new URL('../../test/fixtures/census-recipes/known/', import.meta.url);
17
-
18
- async function makeRepository() {
19
- const root = await mkdtemp(join(tmpdir(), 'awk-census-'));
20
- await cp(FIXTURE, root, { recursive: true });
21
- await exec('git', ['init', '--quiet'], { cwd: root });
22
- await exec('git', ['add', '.'], { cwd: root });
23
- await exec('git', ['add', '-f', 'ignored/src/ignored.mjs'], { cwd: root });
24
- return root;
25
- }
26
-
27
- test('known recipe scans are byte-identical and separate denominator from evidence', async () => {
28
- const root = await makeRepository();
29
- try {
30
- const first = await scanCensus({ repoRoot: root });
31
- const second = await scanCensus({ repoRoot: root });
32
-
33
- assert.equal(serializeCensus(first), serializeCensus(second));
34
- assert.deepEqual(first.denominator.map(({ path }) => path), [
35
- 'package.json',
36
- 'src/index.mjs',
37
- ]);
38
- assert.deepEqual(first.evidence.map(({ path }) => path), [
39
- 'docs/architecture.md',
40
- 'test/index.test.mjs',
41
- ]);
42
- assert.equal(first.fingerprints.builder.length, 64);
43
- assert.equal(first.fingerprints.topology.length, 64);
44
- } finally {
45
- await rm(root, { recursive: true, force: true });
46
- }
47
- });
48
-
49
- test('scan state reflects explicit activation and active-census facts', async () => {
50
- const root = await makeRepository();
51
- try {
52
- assert.equal((await scanCensus({ repoRoot: root })).state, 'disabled');
53
- assert.equal((await scanCensus({ repoRoot: root, enabled: true })).state, 'bootstrap');
54
- assert.equal((await scanCensus({ repoRoot: root, enabled: true, hasActive: true })).state, 'current');
55
-
56
- await mkdir(join(root, 'new-service', 'src'), { recursive: true });
57
- await writeFile(join(root, 'new-service', 'src', 'index.mjs'), 'export const newService = true;\n');
58
- assert.equal(
59
- (await scanCensus({ repoRoot: root, enabled: true, hasActive: true })).state,
60
- 'refresh_required',
61
- );
62
- } finally {
63
- await rm(root, { recursive: true, force: true });
64
- }
65
- });
66
-
67
- test('secret, ignored, generated, and vendor paths are never read or counted', async () => {
68
- const root = await makeRepository();
69
- try {
70
- const reads = [];
71
- const result = await scanCensus({
72
- repoRoot: root,
73
- readText: async (path) => {
74
- reads.push(path);
75
- return (await import('node:fs/promises')).readFile(path, 'utf8');
76
- },
77
- });
78
- const output = serializeCensus(result);
79
-
80
- assert.ok(!reads.some((path) => path.includes('secrets')));
81
- assert.ok(!reads.some((path) => path.includes('credentials')));
82
- assert.ok(!output.includes('CENSUS_SECRET_CANARY'));
83
- assert.ok(!output.includes('secrets/'));
84
- assert.ok(!output.includes('credentials.txt'));
85
- assert.ok(!output.includes('vendor/src'));
86
- assert.ok(!output.includes('dist/src'));
87
- assert.ok(!output.includes('ignored/src'));
88
- assert.equal(result.denominator.length, 2);
89
- } finally {
90
- await rm(root, { recursive: true, force: true });
91
- }
92
- });
93
-
94
- test('tracked product symlinks stay unread and force an open refresh', async () => {
95
- const root = await makeRepository();
96
- const external = await mkdtemp(join(tmpdir(), 'awk-census-external-'));
97
- const externalPath = join(external, 'canary.mjs');
98
- await writeFile(externalPath, 'CENSUS_EXTERNAL_SECRET_CANARY\n');
99
- await symlink(externalPath, join(root, 'src', 'external.mjs'));
100
- await exec('git', ['add', 'src/external.mjs'], { cwd: root });
101
- try {
102
- const reads = [];
103
- const result = await scanCensus({
104
- repoRoot: root,
105
- enabled: true,
106
- hasActive: true,
107
- readText: async (path) => {
108
- reads.push(path);
109
- return (await import('node:fs/promises')).readFile(path, 'utf8');
110
- },
111
- });
112
-
113
- assert.ok(!reads.includes(join(root, 'src', 'external.mjs')));
114
- assert.ok(!reads.includes(externalPath));
115
- assert.ok(!serializeCensus(result).includes('CENSUS_EXTERNAL_SECRET_CANARY'));
116
- assert.ok(!result.denominator.some(({ path }) => path === 'src/external.mjs'));
117
- assert.deepEqual(result.families.surfaces, [
118
- { name: 'production-config', status: 'abgedeckt', type: 'surface' },
119
- { name: 'src', status: 'offen', type: 'surface' },
120
- ]);
121
- assert.equal(result.state, 'refresh_required');
122
- } finally {
123
- await rm(root, { recursive: true, force: true });
124
- await rm(external, { recursive: true, force: true });
125
- }
126
- });
127
-
128
- test('a tracked product file missing from the working tree forces an open refresh', async () => {
129
- const root = await makeRepository();
130
- try {
131
- await rm(join(root, 'src', 'index.mjs'));
132
-
133
- const result = await scanCensus({ repoRoot: root, enabled: true, hasActive: true });
134
-
135
- assert.deepEqual(result.denominator.map(({ path }) => path), ['package.json']);
136
- assert.deepEqual(result.families.surfaces, [
137
- { name: 'production-config', status: 'abgedeckt', type: 'surface' },
138
- { name: 'src', status: 'offen', type: 'surface' },
139
- ]);
140
- assert.equal(result.state, 'refresh_required');
141
- } finally {
142
- await rm(root, { recursive: true, force: true });
143
- }
144
- });
145
-
146
- test('an unreadable tracked product file makes its family open without a duplicate covered verdict', async () => {
147
- const root = await makeRepository();
148
- try {
149
- const result = await scanCensus({
150
- repoRoot: root,
151
- enabled: true,
152
- hasActive: true,
153
- readText: async (path) => {
154
- if (path === join(root, 'src', 'index.mjs')) throw new Error('simulated unreadable product file');
155
- return (await import('node:fs/promises')).readFile(path, 'utf8');
156
- },
157
- });
158
-
159
- assert.deepEqual(result.families.surfaces, [
160
- { name: 'production-config', status: 'abgedeckt', type: 'surface' },
161
- { name: 'src', status: 'offen', type: 'surface' },
162
- ]);
163
- assert.equal(result.state, 'refresh_required');
164
- } finally {
165
- await rm(root, { recursive: true, force: true });
166
- }
167
- });
168
-
169
- test('an untracked source root is open and prevents current', async () => {
170
- const root = await makeRepository();
171
- try {
172
- await mkdir(join(root, 'new-service', 'src'), { recursive: true });
173
- await writeFile(join(root, 'new-service', 'src', 'index.mjs'), 'export const newService = true;\n');
174
-
175
- const result = await scanCensus({ repoRoot: root, enabled: true, hasActive: true });
176
-
177
- assert.equal(result.state, 'refresh_required');
178
- assert.deepEqual(result.families.surfaces.filter(({ status }) => status === 'offen'), [
179
- { name: 'new-service/src', status: 'offen', type: 'surface' },
180
- ]);
181
- } finally {
182
- await rm(root, { recursive: true, force: true });
183
- }
184
- });
185
-
186
- test('delta reports compact path changes and open families', async () => {
187
- const root = await makeRepository();
188
- try {
189
- const previous = await scanCensus({ repoRoot: root });
190
- await writeFile(join(root, 'src', 'index.mjs'), 'export const answer = 43;\n');
191
- await mkdir(join(root, 'new-service', 'src'), { recursive: true });
192
- await writeFile(join(root, 'new-service', 'src', 'index.mjs'), 'export const newService = true;\n');
193
- const next = await scanCensus({ repoRoot: root });
194
-
195
- assert.deepEqual(diffCensus(previous, next), {
196
- added: [],
197
- changed: ['src/index.mjs'],
198
- open: ['new-service/src'],
199
- removed: [],
200
- });
201
- } finally {
202
- await rm(root, { recursive: true, force: true });
203
- }
204
- });
205
-
206
- test('content-only edits do not change the topology fingerprint', async () => {
207
- const root = await makeRepository();
208
- try {
209
- const previous = await scanCensus({ repoRoot: root });
210
- await writeFile(join(root, 'src', 'index.mjs'), 'export const answer = 99;\n');
211
- const next = await scanCensus({ repoRoot: root });
212
-
213
- assert.equal(previous.fingerprints.topology, next.fingerprints.topology);
214
- assert.deepEqual(diffCensus(previous, next).changed, ['src/index.mjs']);
215
- } finally {
216
- await rm(root, { recursive: true, force: true });
217
- }
218
- });
219
-
220
- test('family verdicts expose every machine-readable contract value', () => {
221
- assert.deepEqual(CENSUS_VERDICTS, {
222
- covered: 'abgedeckt',
223
- notRelevant: 'nicht relevant',
224
- open: 'offen',
225
- });
226
- });
227
-
228
- test('behavior families represent covered, not-relevant, and open verdicts separately', async () => {
229
- const root = await makeRepository();
230
- try {
231
- const result = await scanCensus({
232
- repoRoot: root,
233
- enabled: true,
234
- hasActive: true,
235
- behaviorFamilies: [
236
- { name: 'auth-session', status: CENSUS_VERDICTS.covered },
237
- { name: 'billing', status: CENSUS_VERDICTS.notRelevant },
238
- { name: 'email-delivery', status: CENSUS_VERDICTS.open },
239
- ],
240
- });
241
-
242
- assert.deepEqual(result.families.behaviors, [
243
- { name: 'auth-session', status: 'abgedeckt', type: 'behavior' },
244
- { name: 'billing', status: 'nicht relevant', type: 'behavior' },
245
- { name: 'email-delivery', status: 'offen', type: 'behavior' },
246
- ]);
247
- assert.ok(result.families.surfaces.every(({ type }) => type === 'surface'));
248
- assert.equal(result.state, 'refresh_required');
249
- } finally {
250
- await rm(root, { recursive: true, force: true });
251
- }
252
- });
@@ -1,15 +0,0 @@
1
- import { test } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { CENSUS_STATES, resolveCensusState } from './index.mjs';
4
-
5
- test('state resolver implements the complete census state contract', () => {
6
- assert.deepEqual(CENSUS_STATES, [
7
- 'disabled', 'bootstrap', 'current', 'refresh_required', 'updating', 'failed',
8
- ]);
9
- assert.equal(resolveCensusState({ enabled: false }), 'disabled');
10
- assert.equal(resolveCensusState({ enabled: true, hasActive: false }), 'bootstrap');
11
- assert.equal(resolveCensusState({ enabled: true, hasActive: true }), 'current');
12
- assert.equal(resolveCensusState({ enabled: true, hasActive: true, hasOpen: true }), 'refresh_required');
13
- assert.equal(resolveCensusState({ enabled: true, hasActive: true, updating: true }), 'updating');
14
- assert.equal(resolveCensusState({ enabled: true, hasActive: true, failed: true }), 'failed');
15
- });
@@ -1,103 +0,0 @@
1
- import { test } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
4
- import { tmpdir } from 'node:os';
5
- import { join } from 'node:path';
6
- import { activateCensus } from './index.mjs';
7
-
8
- test('activation requires a callable verifier before staging or swapping', async () => {
9
- for (const verify of [undefined, 'not-callable']) {
10
- const root = await mkdtemp(join(tmpdir(), 'awk-census-transaction-'));
11
- const activePath = join(root, 'active.json');
12
- const previous = '{"generation":"previous"}\n';
13
- await writeFile(activePath, previous);
14
- let swapped = false;
15
- try {
16
- await assert.rejects(
17
- activateCensus({
18
- activePath,
19
- candidate: { generation: 'next' },
20
- renameCandidate: async () => { swapped = true; },
21
- verify,
22
- }),
23
- /callable verifier/,
24
- );
25
- assert.equal(await readFile(activePath, 'utf8'), previous);
26
- await assert.rejects(readFile(`${activePath}.candidate`), { code: 'ENOENT' });
27
- await assert.rejects(readFile(`${activePath}.lock`), { code: 'ENOENT' });
28
- assert.equal(swapped, false);
29
- } finally {
30
- await rm(root, { recursive: true, force: true });
31
- }
32
- }
33
- });
34
-
35
- test('verify failure preserves active bytes and a second run can succeed', async () => {
36
- const root = await mkdtemp(join(tmpdir(), 'awk-census-transaction-'));
37
- const activePath = join(root, 'active.json');
38
- const previous = '{"generation":"previous"}\n';
39
- await writeFile(activePath, previous);
40
- try {
41
- await assert.rejects(
42
- activateCensus({
43
- activePath,
44
- candidate: { generation: 'next' },
45
- verify: async () => { throw new Error('fixture verify failure'); },
46
- }),
47
- (error) => error.state === 'failed',
48
- );
49
- assert.equal(await readFile(activePath, 'utf8'), previous);
50
-
51
- const result = await activateCensus({
52
- activePath,
53
- candidate: { generation: 'next' },
54
- verify: async ({ generation }) => generation === 'next',
55
- });
56
- assert.equal(result.state, 'current');
57
- assert.equal(await readFile(activePath, 'utf8'), '{"generation":"next"}\n');
58
- } finally {
59
- await rm(root, { recursive: true, force: true });
60
- }
61
- });
62
-
63
- test('swap failure preserves active bytes', async () => {
64
- const root = await mkdtemp(join(tmpdir(), 'awk-census-transaction-'));
65
- const activePath = join(root, 'active.json');
66
- const previous = '{"generation":"previous"}\n';
67
- await writeFile(activePath, previous);
68
- try {
69
- await assert.rejects(
70
- activateCensus({
71
- activePath,
72
- candidate: { generation: 'next' },
73
- renameCandidate: async () => { throw new Error('fixture swap failure'); },
74
- verify: async () => true,
75
- }),
76
- (error) => error.state === 'failed',
77
- );
78
- assert.equal(await readFile(activePath, 'utf8'), previous);
79
- } finally {
80
- await rm(root, { recursive: true, force: true });
81
- }
82
- });
83
-
84
- test('an existing local lock reports updating without touching active bytes', async () => {
85
- const root = await mkdtemp(join(tmpdir(), 'awk-census-transaction-'));
86
- const activePath = join(root, 'active.json');
87
- const previous = '{"generation":"previous"}\n';
88
- await writeFile(activePath, previous);
89
- await writeFile(`${activePath}.lock`, 'held\n');
90
- try {
91
- await assert.rejects(
92
- activateCensus({
93
- activePath,
94
- candidate: { generation: 'next' },
95
- verify: async () => true,
96
- }),
97
- (error) => error.state === 'updating',
98
- );
99
- assert.equal(await readFile(activePath, 'utf8'), previous);
100
- } finally {
101
- await rm(root, { recursive: true, force: true });
102
- }
103
- });