@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.
- package/.agents/skills/grill-me/SKILL.md +3 -1
- package/.agents/skills/grill-with-docs/SKILL.md +3 -1
- package/.agents/skills/kit-update/SKILL.md +9 -11
- package/.agents/skills/orchestrate-wave/SKILL.md +8 -8
- package/.agents/skills/setup-workflow/SKILL.md +20 -18
- package/.agents/skills/setup-workflow/board-sync.md +4 -1
- package/.agents/skills/setup-workflow/workflow-advisories.md +3 -2
- package/.agents/skills/setup-workflow/worktree-lifecycle.md +36 -31
- package/.agents/skills/to-issues/SKILL.md +43 -0
- package/.agents/skills/wrapup/SKILL.md +98 -54
- package/.claude/skills/grill-me/SKILL.md +3 -1
- package/.claude/skills/grill-me-codex/SKILL.md +3 -1
- package/.claude/skills/grill-with-docs/SKILL.md +3 -1
- package/.claude/skills/grill-with-docs-codex/SKILL.md +3 -1
- package/.claude/skills/kit-update/SKILL.md +9 -11
- package/.claude/skills/orchestrate-wave/SKILL.md +8 -8
- package/.claude/skills/setup-workflow/SKILL.md +20 -18
- package/.claude/skills/setup-workflow/board-sync.md +4 -1
- package/.claude/skills/setup-workflow/workflow-advisories.md +3 -2
- package/.claude/skills/setup-workflow/worktree-lifecycle.md +36 -31
- package/.claude/skills/to-issues/SKILL.md +43 -0
- package/.claude/skills/wrapup/SKILL.md +98 -54
- package/README.md +249 -7
- package/agent-workflow-kit.package.json +42 -42
- package/package.json +16 -12
- package/scripts/marker_lib.py +3 -0
- package/scripts/profile_globs.py +26 -45
- package/scripts/release-delta-guard.mjs +26 -9
- package/scripts/worktree-lifecycle/README.md +57 -160
- package/scripts/worktree-lifecycle/capabilities.json +2 -3
- package/scripts/worktree-lifecycle/classify.py +480 -0
- package/scripts/worktree-lifecycle/cleanup.py +9 -29
- package/scripts/worktree-lifecycle/core.py +68 -1429
- package/scripts/worktree-lifecycle/ignore_seed.py +1 -1
- package/scripts/worktree-lifecycle/profile.py +46 -29
- package/scripts/worktree-lifecycle/setup.py +7 -19
- package/scripts/wrapup-land.py +1292 -545
- package/src/consumer-migrations.json +1 -16
- package/src/lib/bundle.mjs +31 -1
- package/docs/adr/0001-consumer-divergence-policy.md +0 -53
- package/docs/adr/0002-capability-gated-orchestration.md +0 -70
- package/docs/adr/0003-kit-core-and-project-extension-lifecycle.md +0 -63
- package/docs/adr/0004-release-intent-is-a-version-tag.md +0 -64
- package/docs/adr/0005-to-issues-is-the-planning-facade.md +0 -42
- package/docs/adr/0006-routing-knowledge-access-and-policy-are-separate.md +0 -95
- package/docs/adr/0007-session-teardown-requires-provenance-bound-ownership.md +0 -88
- package/docs/adr/0008-planning-ignore-rules-are-offered-never-installed.md +0 -84
- package/docs/adr/0009-teardown-authority-is-stateless-repository-classification.md +0 -101
- package/docs/adr/0010-model-roster-replaces-the-optimization-dial.md +0 -87
- package/docs/agents/board-sync.md +0 -74
- package/docs/agents/code-review.md +0 -14
- package/docs/agents/workflow-capabilities.json +0 -28
- package/docs/research/agent-task-taxonomy-benchmark-coverage.md +0 -451
- package/docs/research/benchlm-routing-source.md +0 -198
- package/docs/research/consumer-owned-protocol-files.md +0 -238
- package/docs/research/frontend-agent-benchmarks.md +0 -282
- package/docs/research/model-effort-routing-benchmarks.md +0 -261
- package/docs/research/provider-neutral-agent-routing.md +0 -207
- package/docs/research/wave-152-consumer-acceptance.md +0 -98
- package/docs/research/wave-43-script-hook-census.md +0 -128
- package/scripts/build-kit.mjs +0 -117
- package/scripts/build-kit.test.mjs +0 -207
- package/scripts/census/census.test.mjs +0 -252
- package/scripts/census/state.test.mjs +0 -15
- package/scripts/census/transaction.test.mjs +0 -103
- package/scripts/census-contract.test.mjs +0 -227
- package/scripts/check-kit-staleness.mjs +0 -45
- package/scripts/check-kit-staleness.test.mjs +0 -19
- package/scripts/codex-exec-scenarios/fake-codex.mjs +0 -165
- package/scripts/codex-exec.test.mjs +0 -871
- package/scripts/grill-census-wiring-guard.mjs +0 -87
- package/scripts/grill-census-wiring-guard.test.mjs +0 -107
- package/scripts/kit-release.test.mjs +0 -273
- package/scripts/kit-update-pr.test.mjs +0 -151
- package/scripts/lib/audit-refs.mjs +0 -146
- package/scripts/lib/scrub.mjs +0 -113
- package/scripts/lib/scrub.test.mjs +0 -187
- package/scripts/memory-lifecycle/memory-lifecycle.test.mjs +0 -170
- package/scripts/portability_profile_scan.py +0 -53
- package/scripts/release-delta-guard.test.mjs +0 -174
- package/scripts/release-parity.test.mjs +0 -90
- package/scripts/release-state.test.mjs +0 -360
- package/scripts/test_anchor_table.py +0 -69
- package/scripts/test_board_bootstrap.py +0 -348
- package/scripts/test_board_sync.py +0 -208
- package/scripts/test_board_sync_create_idempotency.py +0 -117
- package/scripts/test_board_sync_wave_title.py +0 -120
- package/scripts/test_census_backstop.py +0 -712
- package/scripts/test_census_forward_contract.py +0 -91
- package/scripts/test_census_update_contract.test.mjs +0 -454
- package/scripts/test_codex_adapter_sync_contract.py +0 -169
- package/scripts/test_dist_kit_smoke.py +0 -220
- package/scripts/test_drift_guard_diagnostics.py +0 -295
- package/scripts/test_issue_claim_contract.py +0 -169
- package/scripts/test_kit_docs_language_census.py +0 -178
- package/scripts/test_marker_lib.py +0 -162
- package/scripts/test_orchestrate_wave_contract.py +0 -381
- package/scripts/test_pr_body_check.py +0 -264
- package/scripts/test_profile_globs.py +0 -280
- package/scripts/test_program_planning_contract.py +0 -133
- package/scripts/test_release_authorization_contract.py +0 -101
- package/scripts/test_render_anchor.py +0 -267
- package/scripts/test_retro_wrapup_contract.py +0 -153
- package/scripts/test_skill_code_review_seed.py +0 -89
- package/scripts/test_skill_codex_exec_lifecycle.py +0 -123
- package/scripts/test_skill_frontmatter_lint.py +0 -234
- package/scripts/test_skill_gh_lint.py +0 -124
- package/scripts/test_skill_language_census.py +0 -254
- package/scripts/test_skill_optional_readiness.py +0 -171
- package/scripts/test_skill_portability_lint.py +0 -866
- package/scripts/test_skill_precommit_template.py +0 -30
- package/scripts/test_skill_publish_audit.py +0 -358
- package/scripts/test_skill_readiness_contract.py +0 -264
- package/scripts/test_skill_readiness_preflight.py +0 -180
- package/scripts/test_skill_required_readiness.py +0 -233
- package/scripts/test_skill_selfcontainment_lint.py +0 -255
- package/scripts/test_skill_setup_workflow_seeds.py +0 -691
- package/scripts/test_skill_stale_name_lint.py +0 -126
- package/scripts/test_skill_surface_refs.py +0 -300
- package/scripts/test_skill_trailing_artifact_lint.py +0 -71
- package/scripts/test_tdd_contract.py +0 -78
- package/scripts/test_worktree_ignore_seed.py +0 -320
- package/scripts/test_worktree_setup_base_guard.py +0 -140
- package/scripts/test_worktree_wrapup_contract.py +0 -1737
- package/scripts/test_wrapup_land.py +0 -428
- 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
|
-
});
|