@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,204 +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 } 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
- const packedByPath = new Map(JSON.parse(output)[0].files.map((file) => [file.path, file]));
159
- assert.deepEqual(
160
- WAVE_152_HELPERS.map(({ path, mode }) => ({ path, mode: packedByPath.get(path)?.mode })),
161
- WAVE_152_HELPERS.map(({ path, mode }) => ({ path, mode })),
162
- );
163
- assert.ok(files.every((path) => !path.startsWith('.claude/logs/')));
164
- assert.ok(files.every((path) => !path.includes('__pycache__') && !path.endsWith('.pyc')));
165
- const pkg = JSON.parse(execFileSync('node', ['-p', 'JSON.stringify(require("./package.json"))'], {
166
- cwd: REPO, encoding: 'utf8',
167
- }));
168
- assert.ok(pkg.files.includes('scripts/'), 'package must ship future script file types');
169
- assert.equal(await readFile(sentinelLog, 'utf8'), sentinelBytes);
170
- } finally {
171
- await rm(ownedLogDir, { recursive: true, force: true });
172
- }
173
- });
174
-
175
- test('packed scoped artifact keeps the existing npx default-bin inference', async () => {
176
- const destination = await mkdtemp(join(tmpdir(), 'awkit-pack-'));
177
- try {
178
- const packed = JSON.parse(execFileSync('npm', [
179
- 'pack', '--pack-destination', destination, '--json',
180
- ], { cwd: REPO, encoding: 'utf8' }));
181
- const packedByPath = new Map(packed[0].files.map((file) => [file.path, file]));
182
- execFileSync('tar', ['-xzf', join(destination, packed[0].filename), '-C', destination]);
183
- for (const { path, mode } of WAVE_152_HELPERS) {
184
- assert.equal(packedByPath.get(path)?.mode, mode, `packed mode mismatch: ${path}`);
185
- assert.equal(
186
- (await stat(join(destination, 'package', path))).mode & 0o777,
187
- mode,
188
- `extracted mode mismatch: ${path}`,
189
- );
190
- }
191
- const output = execFileSync('npx', [
192
- '--yes', '--offline', `./${packed[0].filename}`,
193
- ], { cwd: destination, encoding: 'utf8' });
194
- assert.match(output, /Usage: agent-workflow-kit/);
195
-
196
- const updater = execFileSync('npm', [
197
- 'exec', '--yes', '--offline', `--package=./${packed[0].filename}`,
198
- '--', 'agent-workflow-kit-update-pr', 'help',
199
- ], { cwd: destination, encoding: 'utf8' });
200
- assert.match(updater, /Usage: agent-workflow-kit-update-pr/);
201
- } finally {
202
- await rm(destination, { recursive: true, force: true });
203
- }
204
- });
@@ -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
- });