@applica-software-guru/sdd-core 1.0.0 → 1.3.3

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 (58) hide show
  1. package/dist/agent/agent-defaults.d.ts +3 -0
  2. package/dist/agent/agent-defaults.d.ts.map +1 -0
  3. package/dist/agent/agent-defaults.js +13 -0
  4. package/dist/agent/agent-defaults.js.map +1 -0
  5. package/dist/agent/agent-runner.d.ts +9 -0
  6. package/dist/agent/agent-runner.d.ts.map +1 -0
  7. package/dist/agent/agent-runner.js +43 -0
  8. package/dist/agent/agent-runner.js.map +1 -0
  9. package/dist/index.d.ts +10 -5
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +11 -1
  12. package/dist/index.js.map +1 -1
  13. package/dist/prompt/apply-prompt-generator.d.ts +3 -0
  14. package/dist/prompt/apply-prompt-generator.d.ts.map +1 -0
  15. package/dist/prompt/apply-prompt-generator.js +48 -0
  16. package/dist/prompt/apply-prompt-generator.js.map +1 -0
  17. package/dist/prompt/prompt-generator.d.ts.map +1 -1
  18. package/dist/prompt/prompt-generator.js +7 -11
  19. package/dist/prompt/prompt-generator.js.map +1 -1
  20. package/dist/scaffold/init.d.ts +1 -1
  21. package/dist/scaffold/init.d.ts.map +1 -1
  22. package/dist/scaffold/init.js +10 -29
  23. package/dist/scaffold/init.js.map +1 -1
  24. package/dist/scaffold/skill-adapters.d.ts +39 -0
  25. package/dist/scaffold/skill-adapters.d.ts.map +1 -0
  26. package/dist/scaffold/skill-adapters.js +224 -0
  27. package/dist/scaffold/skill-adapters.js.map +1 -0
  28. package/dist/scaffold/templates.d.ts +5 -1
  29. package/dist/scaffold/templates.d.ts.map +1 -1
  30. package/dist/scaffold/templates.js +203 -55
  31. package/dist/scaffold/templates.js.map +1 -1
  32. package/dist/sdd.d.ts +7 -3
  33. package/dist/sdd.d.ts.map +1 -1
  34. package/dist/sdd.js +36 -18
  35. package/dist/sdd.js.map +1 -1
  36. package/dist/types.d.ts +2 -0
  37. package/dist/types.d.ts.map +1 -1
  38. package/package.json +1 -1
  39. package/src/agent/agent-defaults.ts +12 -0
  40. package/src/agent/agent-runner.ts +54 -0
  41. package/src/index.ts +17 -5
  42. package/src/prompt/apply-prompt-generator.ts +58 -0
  43. package/src/prompt/prompt-generator.ts +8 -18
  44. package/src/scaffold/init.ts +17 -38
  45. package/src/scaffold/skill-adapters.ts +322 -0
  46. package/src/scaffold/templates.ts +207 -54
  47. package/src/sdd.ts +57 -31
  48. package/src/types.ts +2 -0
  49. package/tests/apply.test.ts +119 -0
  50. package/tests/integration.test.ts +94 -51
  51. package/dist/delta/hasher.d.ts +0 -2
  52. package/dist/delta/hasher.d.ts.map +0 -1
  53. package/dist/delta/hasher.js +0 -8
  54. package/dist/delta/hasher.js.map +0 -1
  55. package/dist/lock/lock-manager.d.ts +0 -6
  56. package/dist/lock/lock-manager.d.ts.map +0 -1
  57. package/dist/lock/lock-manager.js +0 -39
  58. package/dist/lock/lock-manager.js.map +0 -1
@@ -1,13 +1,17 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import { mkdtemp, rm, writeFile, readFile, mkdir } from 'node:fs/promises';
3
- import { existsSync } from 'node:fs';
4
- import { tmpdir } from 'node:os';
5
- import { join } from 'node:path';
6
- import { execSync } from 'node:child_process';
7
- import { SDD } from '../src/sdd.js';
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import { mkdtemp, rm, writeFile, readFile, mkdir } from "node:fs/promises";
3
+ import { existsSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { execSync } from "node:child_process";
7
+ import { SDD } from "../src/sdd.js";
8
8
 
9
9
  function git(cmd: string, cwd: string): string {
10
- return execSync(`git ${cmd}`, { cwd, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
10
+ return execSync(`git ${cmd}`, {
11
+ cwd,
12
+ encoding: "utf-8",
13
+ stdio: ["pipe", "pipe", "pipe"],
14
+ }).trim();
11
15
  }
12
16
 
13
17
  const VISION_MD = `---
@@ -23,110 +27,149 @@ version: "1.0"
23
27
  A test project.
24
28
  `;
25
29
 
26
- describe('SDD integration', () => {
30
+ describe("SDD integration", () => {
27
31
  let tempDir: string;
28
32
 
29
33
  beforeEach(async () => {
30
- tempDir = await mkdtemp(join(tmpdir(), 'sdd-integration-'));
34
+ tempDir = await mkdtemp(join(tmpdir(), "sdd-integration-"));
31
35
  });
32
36
 
33
37
  afterEach(async () => {
34
38
  await rm(tempDir, { recursive: true });
35
39
  });
36
40
 
37
- it('init creates .sdd directory with config and git repo', async () => {
41
+ it("init creates .sdd directory with config and git repo", async () => {
38
42
  const sdd = new SDD({ root: tempDir });
39
43
 
40
- const created = await sdd.init({ description: 'A test app' });
41
- expect(created).toContain('.sdd/config.yaml');
42
- expect(created).toContain('INSTRUCTIONS.md');
43
- expect(existsSync(join(tempDir, '.sdd'))).toBe(true);
44
- expect(existsSync(join(tempDir, '.git'))).toBe(true);
45
- expect(existsSync(join(tempDir, 'product'))).toBe(true);
46
- expect(existsSync(join(tempDir, 'system'))).toBe(true);
47
- expect(existsSync(join(tempDir, 'code'))).toBe(true);
44
+ const created = await sdd.init({ description: "A test app" });
45
+ expect(created).toContain(".sdd/config.yaml");
46
+ expect(created).toContain(".sdd/skill/sdd/SKILL.md");
47
+ expect(created).toContain(".claude/skills/sdd/SKILL.md");
48
+ expect(existsSync(join(tempDir, ".sdd"))).toBe(true);
49
+ expect(existsSync(join(tempDir, ".git"))).toBe(true);
50
+ expect(existsSync(join(tempDir, "product"))).toBe(true);
51
+ expect(existsSync(join(tempDir, "system"))).toBe(true);
52
+ expect(existsSync(join(tempDir, "code"))).toBe(true);
53
+ expect(existsSync(join(tempDir, ".sdd/skill/sdd/SKILL.md"))).toBe(true);
54
+ expect(
55
+ existsSync(join(tempDir, ".sdd/skill/sdd/references/file-format.md")),
56
+ ).toBe(true);
57
+ expect(
58
+ existsSync(join(tempDir, ".sdd/skill/sdd/references/change-requests.md")),
59
+ ).toBe(true);
60
+ expect(existsSync(join(tempDir, ".sdd/skill/sdd/references/bugs.md"))).toBe(
61
+ true,
62
+ );
63
+ expect(existsSync(join(tempDir, ".claude/skills/sdd/SKILL.md"))).toBe(true);
64
+ expect(
65
+ existsSync(join(tempDir, ".claude/skills/sdd/references/file-format.md")),
66
+ ).toBe(true);
67
+ expect(
68
+ existsSync(
69
+ join(tempDir, ".claude/skills/sdd/references/change-requests.md"),
70
+ ),
71
+ ).toBe(true);
72
+ expect(
73
+ existsSync(join(tempDir, ".claude/skills/sdd/references/bugs.md")),
74
+ ).toBe(true);
48
75
 
49
76
  const config = await sdd.config();
50
- expect(config.description).toBe('A test app');
77
+ expect(config.description).toBe("A test app");
51
78
  });
52
79
 
53
- it('full workflow: init → add doc → status → sync → mark-synced', async () => {
80
+ it("full workflow: init → add doc → status → sync → mark-synced", async () => {
54
81
  const sdd = new SDD({ root: tempDir });
55
- await sdd.init({ description: 'test' });
82
+ await sdd.init({ description: "test" });
56
83
  git('config user.email "test@test.com"', tempDir);
57
84
  git('config user.name "Test"', tempDir);
58
85
 
59
86
  // Simulate user creating a doc file
60
- await writeFile(join(tempDir, 'product/vision.md'), VISION_MD, 'utf-8');
87
+ await writeFile(join(tempDir, "product/vision.md"), VISION_MD, "utf-8");
61
88
 
62
89
  // Status — file should be "new"
63
90
  const status = await sdd.status();
64
91
  expect(status.files.length).toBe(1);
65
- expect(status.files[0].status).toBe('new');
92
+ expect(status.files[0].status).toBe("new");
66
93
 
67
94
  // Sync — prompt should list the new file
68
95
  const prompt = await sdd.sync();
69
- expect(prompt).toContain('# SDD Sync Prompt');
70
- expect(prompt).toContain('product/vision.md');
71
- expect(prompt).toContain('**new**');
96
+ expect(prompt).toContain("# SDD Sync Prompt");
97
+ expect(prompt).toContain("product/vision.md");
98
+ expect(prompt).toContain("**new**");
72
99
 
73
100
  // Validate
74
101
  const validation = await sdd.validate();
75
102
  expect(validation.valid).toBe(true);
76
103
 
77
104
  // Mark synced — single file
78
- const marked = await sdd.markSynced(['product/vision.md']);
79
- expect(marked).toContain('product/vision.md');
105
+ const marked = await sdd.markSynced(["product/vision.md"]);
106
+ expect(marked).toContain("product/vision.md");
80
107
 
81
108
  // Agent commits after mark-synced
82
- git('add .', tempDir);
109
+ git("add .", tempDir);
83
110
  git('commit -m "sync: vision"', tempDir);
84
111
 
85
112
  // After commit, status should be synced
86
113
  const statusAfter = await sdd.status();
87
- expect(statusAfter.files[0].status).toBe('synced');
114
+ expect(statusAfter.files[0].status).toBe("synced");
88
115
  });
89
116
 
90
- it('mark-synced with changed status', async () => {
117
+ it("mark-synced with changed status", async () => {
91
118
  const sdd = new SDD({ root: tempDir });
92
- await sdd.init({ description: 'test' });
119
+ await sdd.init({ description: "test" });
93
120
  git('config user.email "test@test.com"', tempDir);
94
121
  git('config user.name "Test"', tempDir);
95
122
 
96
- const changedMd = VISION_MD.replace('status: new', 'status: changed');
97
- await writeFile(join(tempDir, 'product/vision.md'), changedMd, 'utf-8');
123
+ const changedMd = VISION_MD.replace("status: new", "status: changed");
124
+ await writeFile(join(tempDir, "product/vision.md"), changedMd, "utf-8");
98
125
 
99
126
  const marked = await sdd.markSynced();
100
- expect(marked).toContain('product/vision.md');
127
+ expect(marked).toContain("product/vision.md");
101
128
 
102
- const content = await readFile(join(tempDir, 'product/vision.md'), 'utf-8');
103
- expect(content).toContain('status: synced');
129
+ const content = await readFile(join(tempDir, "product/vision.md"), "utf-8");
130
+ expect(content).toContain("status: synced");
104
131
  });
105
132
 
106
- it('mark-synced with deleted status removes the file', async () => {
133
+ it("mark-synced with deleted status removes the file", async () => {
107
134
  const sdd = new SDD({ root: tempDir });
108
- await sdd.init({ description: 'test' });
135
+ await sdd.init({ description: "test" });
109
136
  git('config user.email "test@test.com"', tempDir);
110
137
  git('config user.name "Test"', tempDir);
111
138
 
112
- const deletedMd = VISION_MD.replace('status: new', 'status: deleted');
113
- await writeFile(join(tempDir, 'product/vision.md'), deletedMd, 'utf-8');
139
+ const deletedMd = VISION_MD.replace("status: new", "status: deleted");
140
+ await writeFile(join(tempDir, "product/vision.md"), deletedMd, "utf-8");
114
141
 
115
142
  const marked = await sdd.markSynced();
116
- expect(marked[0]).toContain('removed');
117
- expect(existsSync(join(tempDir, 'product/vision.md'))).toBe(false);
143
+ expect(marked[0]).toContain("removed");
144
+ expect(existsSync(join(tempDir, "product/vision.md"))).toBe(false);
118
145
  });
119
146
 
120
- it('throws when project not initialized', async () => {
147
+ it("throws when project not initialized", async () => {
121
148
  const sdd = new SDD({ root: tempDir });
122
- await expect(sdd.status()).rejects.toThrow('No SDD project found');
149
+ await expect(sdd.status()).rejects.toThrow("No SDD project found");
123
150
  });
124
151
 
125
- it('init is idempotent for INSTRUCTIONS.md', async () => {
152
+ it("init is idempotent for canonical and adapter skill files", async () => {
126
153
  const sdd = new SDD({ root: tempDir });
127
- const first = await sdd.init({ description: 'test' });
128
- const second = await sdd.init({ description: 'test' });
129
- expect(first).toContain('INSTRUCTIONS.md');
130
- expect(second).not.toContain('INSTRUCTIONS.md');
154
+ const first = await sdd.init({ description: "test" });
155
+ const second = await sdd.init({ description: "test" });
156
+ expect(first).toContain(".sdd/skill/sdd/SKILL.md");
157
+ expect(first).toContain(".claude/skills/sdd/SKILL.md");
158
+ expect(second).not.toContain(".sdd/skill/sdd/SKILL.md");
159
+ expect(second).not.toContain(".claude/skills/sdd/SKILL.md");
160
+ });
161
+
162
+ it("syncAdapters supports dry-run and selective adapters", async () => {
163
+ const sdd = new SDD({ root: tempDir });
164
+ await sdd.init({ description: "test" });
165
+
166
+ const dryRun = await sdd.syncAdapters({ agents: ["claude"], dryRun: true });
167
+ expect(dryRun.selectedAgents).toEqual(["claude"]);
168
+ expect(
169
+ dryRun.adapters.some((c) => c.path === ".claude/skills/sdd/SKILL.md"),
170
+ ).toBe(true);
171
+
172
+ await sdd.syncAdapters({ agents: ["claude"] });
173
+ expect(existsSync(join(tempDir, ".claude/skills/sdd/SKILL.md"))).toBe(true);
131
174
  });
132
175
  });
@@ -1,2 +0,0 @@
1
- export declare function sha256(content: string): string;
2
- //# sourceMappingURL=hasher.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"hasher.d.ts","sourceRoot":"","sources":["../../src/delta/hasher.ts"],"names":[],"mappings":"AAEA,wBAAgB,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE9C"}
@@ -1,8 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.sha256 = sha256;
4
- const node_crypto_1 = require("node:crypto");
5
- function sha256(content) {
6
- return (0, node_crypto_1.createHash)('sha256').update(content).digest('hex');
7
- }
8
- //# sourceMappingURL=hasher.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"hasher.js","sourceRoot":"","sources":["../../src/delta/hasher.ts"],"names":[],"mappings":";;AAEA,wBAEC;AAJD,6CAAyC;AAEzC,SAAgB,MAAM,CAAC,OAAe;IACpC,OAAO,IAAA,wBAAU,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC5D,CAAC"}
@@ -1,6 +0,0 @@
1
- import type { LockFile } from '../types.js';
2
- export declare function lockFilePath(root: string): string;
3
- export declare function readLockFile(root: string): Promise<LockFile>;
4
- export declare function writeLockFile(root: string, lock: LockFile): Promise<void>;
5
- export declare function createEmptyLock(): LockFile;
6
- //# sourceMappingURL=lock-manager.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"lock-manager.d.ts","sourceRoot":"","sources":["../../src/lock/lock-manager.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAK5C,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAQlE;AAED,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAI/E;AAED,wBAAgB,eAAe,IAAI,QAAQ,CAK1C"}
@@ -1,39 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.lockFilePath = lockFilePath;
7
- exports.readLockFile = readLockFile;
8
- exports.writeLockFile = writeLockFile;
9
- exports.createEmptyLock = createEmptyLock;
10
- const promises_1 = require("node:fs/promises");
11
- const node_fs_1 = require("node:fs");
12
- const node_path_1 = require("node:path");
13
- const js_yaml_1 = __importDefault(require("js-yaml"));
14
- const config_manager_js_1 = require("../config/config-manager.js");
15
- const LOCK_FILENAME = 'lock.yaml';
16
- function lockFilePath(root) {
17
- return (0, node_path_1.resolve)(root, config_manager_js_1.SDD_DIR, LOCK_FILENAME);
18
- }
19
- async function readLockFile(root) {
20
- const path = lockFilePath(root);
21
- if (!(0, node_fs_1.existsSync)(path)) {
22
- return createEmptyLock();
23
- }
24
- const content = await (0, promises_1.readFile)(path, 'utf-8');
25
- const parsed = js_yaml_1.default.load(content);
26
- return parsed ?? createEmptyLock();
27
- }
28
- async function writeLockFile(root, lock) {
29
- const path = lockFilePath(root);
30
- const content = js_yaml_1.default.dump(lock, { sortKeys: true, lineWidth: -1 });
31
- await (0, promises_1.writeFile)(path, content, 'utf-8');
32
- }
33
- function createEmptyLock() {
34
- return {
35
- 'synced-at': new Date().toISOString(),
36
- files: {},
37
- };
38
- }
39
- //# sourceMappingURL=lock-manager.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"lock-manager.js","sourceRoot":"","sources":["../../src/lock/lock-manager.ts"],"names":[],"mappings":";;;;;AASA,oCAEC;AAED,oCAQC;AAED,sCAIC;AAED,0CAKC;AAlCD,+CAAuD;AACvD,qCAAqC;AACrC,yCAAoC;AACpC,sDAA2B;AAE3B,mEAAsD;AAEtD,MAAM,aAAa,GAAG,WAAW,CAAC;AAElC,SAAgB,YAAY,CAAC,IAAY;IACvC,OAAO,IAAA,mBAAO,EAAC,IAAI,EAAE,2BAAO,EAAE,aAAa,CAAC,CAAC;AAC/C,CAAC;AAEM,KAAK,UAAU,YAAY,CAAC,IAAY;IAC7C,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,CAAC,IAAA,oBAAU,EAAC,IAAI,CAAC,EAAE,CAAC;QACtB,OAAO,eAAe,EAAE,CAAC;IAC3B,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,IAAA,mBAAQ,EAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC9C,MAAM,MAAM,GAAG,iBAAI,CAAC,IAAI,CAAC,OAAO,CAAoB,CAAC;IACrD,OAAO,MAAM,IAAI,eAAe,EAAE,CAAC;AACrC,CAAC;AAEM,KAAK,UAAU,aAAa,CAAC,IAAY,EAAE,IAAc;IAC9D,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAChC,MAAM,OAAO,GAAG,iBAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;IACnE,MAAM,IAAA,oBAAS,EAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAC1C,CAAC;AAED,SAAgB,eAAe;IAC7B,OAAO;QACL,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACrC,KAAK,EAAE,EAAE;KACV,CAAC;AACJ,CAAC"}