@haposoft/cafekit 0.18.3 → 0.18.4

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 (39) hide show
  1. package/README.md +1 -1
  2. package/bin/__tests__/adv-skill.test.js +1 -2
  3. package/bin/__tests__/claude-plugin-build.test.js +2 -2
  4. package/bin/__tests__/codex-hook-runtime-fixture.cjs +26 -0
  5. package/bin/__tests__/codex-hooks-ownership.test.js +8 -11
  6. package/bin/__tests__/codex-hooks.test.js +49 -79
  7. package/bin/__tests__/codex-implicit-policy.test.js +2 -2
  8. package/bin/__tests__/codex-native-hook-boundaries.test.js +2 -4
  9. package/bin/__tests__/codex-native.test.js +125 -2294
  10. package/bin/__tests__/codex-quiet-hooks.test.js +2 -8
  11. package/bin/__tests__/codex-skill-projections.test.js +101 -0
  12. package/bin/__tests__/develop-contract.test.js +100 -874
  13. package/bin/__tests__/orca-session.test.js +2 -16
  14. package/bin/__tests__/package-inventory.test.js +228 -2044
  15. package/bin/__tests__/secret-scanner.test.js +73 -0
  16. package/bin/__tests__/skill-routing-source.test.js +6 -0
  17. package/bin/__tests__/toaa-skill.test.js +195 -0
  18. package/bin/__tests__/validator-grounding.test.js +0 -198
  19. package/bin/__tests__/vibe-skill.test.js +1 -2
  20. package/bin/__tests__/website-docs.test.js +58 -0
  21. package/bin/lib/codex-install.js +5 -3
  22. package/package.json +1 -1
  23. package/src/claude/hooks/__tests__/privacy-block.test.js +0 -26
  24. package/src/claude/hooks/__tests__/spec-gate.test.js +0 -47
  25. package/src/claude/hooks/__tests__/state.test.js +21 -0
  26. package/src/claude/hooks/state.cjs +22 -0
  27. package/src/claude/migration-manifest.json +1 -0
  28. package/src/claude/scripts/scan-staged-secrets.cjs +49 -3
  29. package/src/claude/skills/develop/SKILL.md +6 -0
  30. package/src/claude/skills/sync/SKILL.md +4 -0
  31. package/src/claude/skills/toaa/SKILL.md +82 -0
  32. package/src/claude/skills/toaa/references/autonomous-orchestration.md +54 -0
  33. package/src/claude/skills/toaa/references/prerequisites-checklist.md +37 -0
  34. package/src/codex/hooks/lib/hook-context.cjs +1 -0
  35. package/src/codex/hooks/lib/state-store.cjs +27 -3
  36. package/bin/__tests__/codex-research-projection.test.js +0 -70
  37. package/bin/__tests__/codex-specs-projection.test.js +0 -76
  38. package/bin/__tests__/specs-v2-execution-closeout.test.js +0 -58
  39. package/src/claude/skills/chrome-devtools/scripts/__tests__/error-handling.test.js +0 -102
@@ -7,19 +7,13 @@ const os = require('node:os');
7
7
  const path = require('node:path');
8
8
  const { spawnSync } = require('node:child_process');
9
9
  const { test } = require('node:test');
10
+ const { installCodexHookRuntime } = require('./codex-hook-runtime-fixture.cjs');
10
11
  const SOURCE = path.resolve(__dirname, '../../src');
11
12
 
12
13
  function fixture({ specs = 'specs', packets = ['auth-flow'] } = {}) {
13
14
  const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ck-codex-quiet-'));
14
15
  const hooks = path.join(root, '.codex/hooks');
15
- fs.cpSync(path.join(SOURCE, 'codex/hooks'), hooks, { recursive: true });
16
- for (const name of ['privacy-paths.cjs', 'privacy-command-analysis.cjs']) {
17
- fs.copyFileSync(path.join(SOURCE, 'claude/hooks/lib', name), path.join(hooks, 'lib', name));
18
- }
19
- fs.copyFileSync(path.join(SOURCE, 'claude/hooks/lib/spec-touch.cjs'), path.join(hooks, 'lib/spec-touch.cjs'));
20
- fs.copyFileSync(path.join(SOURCE, 'claude/hooks/lib/secret-keywords.cjs'), path.join(hooks, 'lib/secret-keywords.cjs'));
21
- fs.copyFileSync(path.join(SOURCE, 'claude/hooks/lib/update-check.cjs'), path.join(hooks, 'lib/update-check.cjs'));
22
- fs.copyFileSync(path.join(SOURCE, 'claude/hooks/lib/runtime-path-safety.cjs'), path.join(hooks, 'lib/runtime-path-safety.cjs'));
16
+ installCodexHookRuntime(root);
23
17
  fs.cpSync(path.join(SOURCE, 'claude/scripts'), path.join(root, '.codex/scripts'), { recursive: true });
24
18
  fs.writeFileSync(path.join(root, '.codex/runtime.json'), JSON.stringify({ paths: { specs } }));
25
19
  fs.writeFileSync(path.join(root, '.gitignore'), '.codex/\n');
@@ -0,0 +1,101 @@
1
+ 'use strict';
2
+
3
+ const assert = require('node:assert/strict');
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
+ const { test } = require('node:test');
7
+ const {
8
+ normalizeCodexBody,
9
+ CODEX_SPECS_SCOPE_MARKER, CODEX_SPECS_SCOPE_NOTE,
10
+ CODEX_RESEARCH_DEPTH_MARKER, CODEX_RESEARCH_DEPTH_NOTE,
11
+ } = require('../lib/codex-install');
12
+ const { splitFrontmatter } = require('../lib/codex-frontmatter');
13
+
14
+ const SKILLS = path.resolve(__dirname, '../../src/claude/skills');
15
+
16
+ // Skills that Codex projects with an extra note inserted after the frontmatter.
17
+ const PROJECTED = [
18
+ { skill: 'specs', codexName: 'cf-specs', marker: CODEX_SPECS_SCOPE_MARKER, note: CODEX_SPECS_SCOPE_NOTE },
19
+ { skill: 'research', codexName: 'cf-research', marker: CODEX_RESEARCH_DEPTH_MARKER, note: CODEX_RESEARCH_DEPTH_NOTE },
20
+ ].map((entry) => {
21
+ const file = path.join(SKILLS, entry.skill, 'SKILL.md');
22
+ return { ...entry, file, source: fs.readFileSync(file, 'utf8') };
23
+ });
24
+
25
+ // Ask carries its own boundary note; every other skill body is projected unchanged.
26
+ const OWN_PROJECTION = new Set([...PROJECTED.map(({ file }) => file), path.join(SKILLS, 'ask/SKILL.md')]);
27
+
28
+ function skillFiles(directory) {
29
+ return fs.readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
30
+ const file = path.join(directory, entry.name);
31
+ return entry.isDirectory() ? skillFiles(file) : entry.name === 'SKILL.md' ? [file] : [];
32
+ });
33
+ }
34
+
35
+ const occurrences = (text, marker) => text.split(marker).length - 1;
36
+
37
+ for (const { skill, codexName, marker, note, file, source } of PROJECTED) {
38
+ test(`codex ${skill} projection carries its note immediately after unchanged frontmatter`, () => {
39
+ assert.ok(typeof marker === 'string' && marker.length > 0);
40
+ assert.equal(typeof note, 'string');
41
+ const plain = normalizeCodexBody(source);
42
+ const projected = normalizeCodexBody(source, file);
43
+ assert.match(projected, /^---\r?\n/);
44
+ const before = splitFrontmatter(plain), after = splitFrontmatter(projected);
45
+ assert.equal(after.frontmatter.name, codexName);
46
+ assert.deepEqual(after.frontmatter, before.frontmatter);
47
+ assert.equal(projected.slice(0, projected.length - after.body.length), plain.slice(0, plain.length - before.body.length));
48
+ assert.equal(after.body, `\n${note}\n\n${before.body}`);
49
+ assert.equal(occurrences(projected, marker), 1);
50
+ assert.ok(note.includes(marker));
51
+ assert.doesNotMatch(note, /\b(?:Write|Edit|Read|Bash|WebSearch|WebFetch|AskUserQuestion)\b/);
52
+ });
53
+
54
+ test(`codex ${skill} note is scoped to the exact skill source path and supports Windows separators`, () => {
55
+ for (const sourcePath of ['', `skills/${skill}/references/templates.md`, `other-skills/${skill}/SKILL.md`]) {
56
+ const projected = normalizeCodexBody(source, sourcePath);
57
+ assert.ok(!projected.includes(marker), sourcePath);
58
+ assert.equal(projected, normalizeCodexBody(source), sourcePath);
59
+ }
60
+ assert.ok(normalizeCodexBody(source, `C:\\project\\skills\\${skill}\\SKILL.md`).includes(marker));
61
+ });
62
+ }
63
+
64
+ test('each projection marker appears only in its own skill and other skill bodies stay unchanged', () => {
65
+ const all = skillFiles(SKILLS);
66
+ for (const name of ['ask', 'research', 'specs']) assert.ok(all.includes(path.join(SKILLS, name, 'SKILL.md')));
67
+ for (const file of all) {
68
+ const source = fs.readFileSync(file, 'utf8');
69
+ const projected = normalizeCodexBody(source, file);
70
+ for (const { marker, file: ownFile } of PROJECTED) {
71
+ assert.equal(occurrences(projected, marker), file === ownFile ? 1 : 0, `${marker} in ${file}`);
72
+ }
73
+ if (!OWN_PROJECTION.has(file)) assert.equal(projected, normalizeCodexBody(source), file);
74
+ }
75
+ });
76
+
77
+ // Projecting an already projected skill must not add its note or description boundary again.
78
+ test('every Codex skill projection is idempotent', () => {
79
+ for (const file of skillFiles(SKILLS)) {
80
+ const projected = normalizeCodexBody(fs.readFileSync(file, 'utf8'), file);
81
+ assert.equal(normalizeCodexBody(projected, file), projected, file);
82
+ }
83
+ });
84
+
85
+ test('codex specs scope note keeps the GATE-SCOPE answer boundary', () => {
86
+ assert.match(CODEX_SPECS_SCOPE_MARKER, /GATE-SCOPE.*final answer/);
87
+ assert.match(CODEX_SPECS_SCOPE_NOTE, /not in commentary or through `request_user_input`/);
88
+ assert.match(CODEX_SPECS_SCOPE_NOTE, /Do not send a summary after/);
89
+ assert.match(CODEX_SPECS_SCOPE_NOTE, /\[NEEDS CLARIFICATION: <question>\]/);
90
+ assert.match(CODEX_SPECS_SCOPE_NOTE, /EXPAND, KEEP, and CUT together/);
91
+ assert.match(CODEX_SPECS_SCOPE_NOTE, /Preserve.*NEEDS CLARIFICATION.*EXPAND.*KEEP.*CUT.*do not translate/);
92
+ assert.doesNotMatch(CODEX_SPECS_SCOPE_NOTE, /customer|sales|notif|khách|bán hàng|thông báo/i);
93
+ });
94
+
95
+ test('codex research depth note keeps the quick-answer boundary', () => {
96
+ assert.match(CODEX_RESEARCH_DEPTH_NOTE, /known option/);
97
+ assert.match(CODEX_RESEARCH_DEPTH_NOTE, /Quick/);
98
+ assert.match(CODEX_RESEARCH_DEPTH_NOTE, /changes the recommendation/);
99
+ assert.match(CODEX_RESEARCH_DEPTH_NOTE, /conflicting evidence/i);
100
+ assert.doesNotMatch(CODEX_RESEARCH_DEPTH_NOTE, /\b(?:Drizzle|Prisma|BullMQ|pg-boss|billing|ADR-004)\b/);
101
+ });