ai-engineering-loop 1.0.11 → 1.0.12

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 (49) hide show
  1. package/.agents/devil-advocate.md +15 -4
  2. package/.agents/judge.md +7 -3
  3. package/.agents/workflows/ai-engineering-loop.md +4 -2
  4. package/.claude/agents/devil-advocate.md +15 -4
  5. package/.claude/agents/judge.md +7 -3
  6. package/.claude/commands/ai-engineering-loop.md +1 -1
  7. package/.claude/skills/ai-engineering-loop/SKILL.md +6 -5
  8. package/.gemini/skills/ai-engineering-loop/SKILL.md +59 -0
  9. package/.grok/agents/devil-advocate.md +15 -4
  10. package/.grok/agents/judge.md +7 -3
  11. package/.grok/commands/ai-engineering-loop.md +1 -1
  12. package/.grok/skills/ai-engineering-loop/SKILL.md +8 -6
  13. package/README.md +31 -3
  14. package/README.npm.md +5 -2
  15. package/adapters/dot/README.md +27 -3
  16. package/adapters/dot/coreview.md +30 -11
  17. package/adapters/dot/mattermost.md +31 -19
  18. package/adapters/dot/skills/dot-dev-skill-router/SKILL.md +55 -0
  19. package/adapters/dot/skills/dot-dev-workflow/SKILL.md +118 -0
  20. package/agents/devil-advocate.md +3 -1
  21. package/agents/maker.md +13 -11
  22. package/agents/shared/devil-advocate.body.md +15 -4
  23. package/agents/shared/judge.body.md +7 -3
  24. package/bin/ai-engineering-loop.js +131 -22
  25. package/core/context-impact-assessment.md +2 -0
  26. package/core/definition-of-done.md +2 -0
  27. package/core/goal-contract.md +18 -4
  28. package/core/grill-policy.md +70 -0
  29. package/core/handoff-policy.md +44 -0
  30. package/core/judge-policy.md +4 -3
  31. package/core/project-initialization.md +2 -1
  32. package/core/repo-config-schema.md +15 -1
  33. package/core/root-cause-analysis.md +30 -0
  34. package/core/verification-loop.md +3 -1
  35. package/examples/initialization/discovery-trace.md +1 -1
  36. package/examples/initialization/generated-context.md +1 -1
  37. package/lib/orchestration.js +20 -2
  38. package/lib/sync-hosts.js +195 -0
  39. package/package.json +2 -1
  40. package/policies/finding-policy.md +7 -1
  41. package/policies/review-budget.md +1 -0
  42. package/policies/tdd-policy.md +32 -0
  43. package/scripts/init.sh +21 -0
  44. package/templates/repo-config/adr-readme.md +33 -0
  45. package/templates/repo-config/glossary.md +18 -0
  46. package/tests/living-context.test.js +46 -0
  47. package/tests/orchestration.test.js +80 -0
  48. package/tests/skill-host-compat.test.js +83 -0
  49. package/tests/sync-hosts.test.js +119 -0
@@ -0,0 +1,119 @@
1
+ const test = require('node:test');
2
+ const assert = require('node:assert');
3
+ const fs = require('fs');
4
+ const os = require('os');
5
+ const path = require('path');
6
+ const { execFileSync } = require('child_process');
7
+
8
+ const {
9
+ applyGrokSkillOverlay,
10
+ planHostSync,
11
+ applyHostSync,
12
+ summarizeHostSync
13
+ } = require('../lib/sync-hosts.js');
14
+
15
+ const ROOT = path.join(__dirname, '..');
16
+ const CLI = path.join(ROOT, 'bin', 'ai-engineering-loop.js');
17
+
18
+ function tmpHome() {
19
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'ael-home-'));
20
+ }
21
+
22
+ test('applyGrokSkillOverlay inserts user-invocable once', () => {
23
+ const raw = '---\nname: demo\ndescription: x\n---\n\n# Body\n';
24
+ const once = applyGrokSkillOverlay(raw);
25
+ assert.match(once, /user-invocable: true/);
26
+ const twice = applyGrokSkillOverlay(once);
27
+ assert.strictEqual(twice, once);
28
+ });
29
+
30
+ test('planHostSync skips everything when no host roots exist', () => {
31
+ const home = tmpHome();
32
+ const plan = planHostSync({ packageRoot: ROOT, home });
33
+ assert.ok(plan.length > 0);
34
+ assert.ok(plan.every((item) => item.action === 'skip'));
35
+ const reasons = new Set(plan.map((item) => item.reason));
36
+ assert.ok(reasons.has('host-missing'));
37
+ });
38
+
39
+ test('sync upserts Claude AEL files but does not invent DOT skills', () => {
40
+ const home = tmpHome();
41
+ fs.mkdirSync(path.join(home, '.claude'));
42
+ const results = applyHostSync({ packageRoot: ROOT, home });
43
+ const claude = results.filter((item) => item.id === 'claude');
44
+ assert.ok(claude.every((item) => item.action === 'copy'));
45
+ assert.ok(fs.existsSync(path.join(home, '.claude/skills/ai-engineering-loop/SKILL.md')));
46
+ assert.ok(fs.existsSync(path.join(home, '.claude/agents/devil-advocate.md')));
47
+ assert.ok(fs.existsSync(path.join(home, '.claude/agents/judge.md')));
48
+ assert.ok(fs.existsSync(path.join(home, '.claude/commands/ai-engineering-loop.md')));
49
+ assert.ok(!fs.existsSync(path.join(home, '.claude/skills/dot-dev-workflow/SKILL.md')));
50
+ assert.ok(!fs.existsSync(path.join(home, '.claude/settings.local.json')));
51
+ });
52
+
53
+ test('DOT skills update only when already installed', () => {
54
+ const home = tmpHome();
55
+ const dest = path.join(home, '.claude/skills/dot-dev-workflow/SKILL.md');
56
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
57
+ fs.writeFileSync(dest, 'stale\n');
58
+ const results = applyHostSync({ packageRoot: ROOT, home });
59
+ const dotWf = results.find((item) => item.dest === dest);
60
+ assert.strictEqual(dotWf.action, 'copy');
61
+ assert.match(fs.readFileSync(dest, 'utf8'), /Not a substitute for ai-engineering-loop/);
62
+ });
63
+
64
+ test('Grok skill overlay keeps user-invocable', () => {
65
+ const home = tmpHome();
66
+ fs.mkdirSync(path.join(home, '.grok'));
67
+ const dot = path.join(home, '.grok/skills/dot-dev-workflow/SKILL.md');
68
+ fs.mkdirSync(path.dirname(dot), { recursive: true });
69
+ fs.writeFileSync(dot, '---\nname: dot-dev-workflow\ndescription: old\n---\n\nold\n');
70
+ applyHostSync({ packageRoot: ROOT, home });
71
+ const skill = fs.readFileSync(path.join(home, '.grok/skills/ai-engineering-loop/SKILL.md'), 'utf8');
72
+ const wf = fs.readFileSync(dot, 'utf8');
73
+ assert.match(skill, /^user-invocable: true$/m);
74
+ assert.match(wf, /^user-invocable: true$/m);
75
+ });
76
+
77
+ test('dry-run does not write; second apply is current', () => {
78
+ const home = tmpHome();
79
+ fs.mkdirSync(path.join(home, '.gemini'));
80
+ const dry = applyHostSync({ packageRoot: ROOT, home, dryRun: true });
81
+ const gemini = dry.find((item) => item.id === 'gemini');
82
+ assert.strictEqual(gemini.action, 'copy');
83
+ assert.ok(gemini.dryRun);
84
+ assert.ok(!fs.existsSync(path.join(home, '.gemini/config/skills/ai-engineering-loop/SKILL.md')));
85
+ applyHostSync({ packageRoot: ROOT, home });
86
+ const again = applyHostSync({ packageRoot: ROOT, home });
87
+ assert.strictEqual(summarizeHostSync(again.filter((item) => item.id === 'gemini')).current, 1);
88
+ assert.strictEqual(summarizeHostSync(again.filter((item) => item.id === 'gemini')).copy, 0);
89
+ });
90
+
91
+ test('symlink destinations are left alone', () => {
92
+ const home = tmpHome();
93
+ const skillDir = path.join(home, '.claude/skills/ai-engineering-loop');
94
+ fs.mkdirSync(skillDir, { recursive: true });
95
+ const target = path.join(home, 'outside.md');
96
+ fs.writeFileSync(target, 'keep me\n');
97
+ fs.symlinkSync(target, path.join(skillDir, 'SKILL.md'));
98
+ applyHostSync({ packageRoot: ROOT, home });
99
+ assert.strictEqual(fs.readFileSync(target, 'utf8'), 'keep me\n');
100
+ assert.ok(fs.lstatSync(path.join(skillDir, 'SKILL.md')).isSymbolicLink());
101
+ });
102
+
103
+ test('CLI sync-hosts --dry-run respects AEL_HOME', () => {
104
+ const home = tmpHome();
105
+ fs.mkdirSync(path.join(home, '.agents'));
106
+ const out = execFileSync('node', [CLI, 'sync-hosts', '--dry-run'], {
107
+ cwd: ROOT,
108
+ encoding: 'utf8',
109
+ env: { ...process.env, AEL_HOME: home }
110
+ });
111
+ assert.match(out, /dry-run/);
112
+ assert.match(out, /would-copy|copy:/);
113
+ assert.ok(!fs.existsSync(path.join(home, '.agents/judge.md')));
114
+ });
115
+
116
+ test('CLI help lists sync-hosts', () => {
117
+ const out = execFileSync('node', [CLI, '--help'], { encoding: 'utf8' });
118
+ assert.match(out, /sync-hosts/);
119
+ });