sinapse-ai 1.12.1 → 1.13.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 (93) hide show
  1. package/.claude/CLAUDE.md +2 -2
  2. package/.claude/rules/agent-memory-imports.md +2 -0
  3. package/.sinapse-ai/cli/commands/pro/index.js +8 -48
  4. package/.sinapse-ai/constitution.md +6 -2
  5. package/.sinapse-ai/core-config.yaml +5 -1
  6. package/.sinapse-ai/data/entity-registry.yaml +95 -95
  7. package/.sinapse-ai/development/agents/architect/MEMORY.md +35 -0
  8. package/.sinapse-ai/development/agents/data-engineer/MEMORY.md +38 -0
  9. package/.sinapse-ai/development/agents/developer/MEMORY.md +36 -0
  10. package/.sinapse-ai/development/agents/devops/MEMORY.md +39 -0
  11. package/.sinapse-ai/development/agents/devops.md +0 -1
  12. package/.sinapse-ai/development/agents/product-lead/MEMORY.md +36 -0
  13. package/.sinapse-ai/development/agents/project-lead/MEMORY.md +36 -0
  14. package/.sinapse-ai/development/agents/quality-gate/MEMORY.md +35 -0
  15. package/.sinapse-ai/development/agents/quality-gate.md +1 -1
  16. package/.sinapse-ai/development/agents/snps-orqx.md +4 -0
  17. package/.sinapse-ai/development/agents/sprint-lead/MEMORY.md +36 -0
  18. package/.sinapse-ai/development/templates/chrome-brain/knowledge-base/chrome-brain.md +2 -2
  19. package/.sinapse-ai/infrastructure/scripts/ide-sync/index.js +15 -0
  20. package/.sinapse-ai/infrastructure/scripts/validate-agents.js +159 -8
  21. package/.sinapse-ai/install-manifest.yaml +20 -156
  22. package/AGENTS.md +13 -2
  23. package/CHANGELOG.md +119 -4
  24. package/README.en.md +1 -1
  25. package/README.md +6 -26
  26. package/bin/cli.js +2 -7
  27. package/bin/commands/install.js +19 -257
  28. package/bin/commands/update.js +18 -78
  29. package/bin/lib/command-generator.js +261 -0
  30. package/bin/lib/prompts.js +2 -65
  31. package/bin/modules/chrome-brain-installer.js +2 -1
  32. package/bin/postinstall.js +6 -6
  33. package/docs/getting-started.md +0 -1
  34. package/docs/pt/getting-started.md +1 -1
  35. package/docs/security/dependabot-triage.md +17 -17
  36. package/package.json +6 -3
  37. package/packages/installer/src/wizard/i18n.js +3 -204
  38. package/packages/installer/src/wizard/index.js +1 -1
  39. package/packages/installer/src/wizard/questions.js +5 -368
  40. package/packages/installer/src/wizard/wizard.js +0 -9
  41. package/scripts/generate-install-manifest.js +6 -0
  42. package/scripts/validate-changelog-tags.js +97 -0
  43. package/scripts/validate-no-personal-leaks.js +184 -28
  44. package/scripts/validate-schemas.js +55 -10
  45. package/scripts/validate-story-meta.js +25 -12
  46. package/squads/claude-code-mastery/agents/claude-mastery-chief.md +1 -1
  47. package/squads/claude-code-mastery/agents/hooks-architect.md +12 -20
  48. package/squads/claude-code-mastery/tasks/audit-setup.md +1 -1
  49. package/squads/squad-product/agents/product-orqx.md +0 -1
  50. package/squads/squad-product/agents/ps-client-product-manager.md +0 -1
  51. package/squads/squad-product/agents/ps-delivery-manager.md +0 -1
  52. package/squads/squad-product/agents/ps-discovery-lead.md +0 -1
  53. package/squads/squad-product/agents/ps-product-analyst.md +0 -1
  54. package/squads/squad-product/agents/ps-product-ops-specialist.md +0 -1
  55. package/squads/squad-product/agents/ps-product-strategist.md +0 -1
  56. package/.sinapse-ai/core/grounding/README.md +0 -83
  57. package/.sinapse-ai/core/grounding/brand.cjs +0 -29
  58. package/.sinapse-ai/core/grounding/config-loader.cjs +0 -52
  59. package/.sinapse-ai/core/grounding/design-system.cjs +0 -29
  60. package/.sinapse-ai/core/grounding/vault.cjs +0 -36
  61. package/.sinapse-ai/development/scripts/approval-workflow.js +0 -643
  62. package/.sinapse-ai/development/scripts/backup-manager.js +0 -607
  63. package/.sinapse-ai/development/scripts/branch-manager.js +0 -390
  64. package/.sinapse-ai/development/scripts/code-quality-improver.js +0 -1329
  65. package/.sinapse-ai/development/scripts/commit-message-generator.js +0 -850
  66. package/.sinapse-ai/development/scripts/conflict-resolver.js +0 -675
  67. package/.sinapse-ai/development/scripts/dependency-analyzer.js +0 -638
  68. package/.sinapse-ai/development/scripts/diff-generator.js +0 -352
  69. package/.sinapse-ai/development/scripts/git-wrapper.js +0 -462
  70. package/.sinapse-ai/development/scripts/modification-validator.js +0 -555
  71. package/.sinapse-ai/development/scripts/performance-analyzer.js +0 -758
  72. package/.sinapse-ai/development/scripts/refactoring-suggester.js +0 -1148
  73. package/.sinapse-ai/development/scripts/rollback-handler.js +0 -531
  74. package/.sinapse-ai/development/scripts/security-checker.js +0 -359
  75. package/.sinapse-ai/development/scripts/template-engine.js +0 -240
  76. package/.sinapse-ai/development/scripts/template-validator.js +0 -279
  77. package/.sinapse-ai/development/scripts/test-generator.js +0 -844
  78. package/.sinapse-ai/development/scripts/transaction-manager.js +0 -590
  79. package/.sinapse-ai/development/scripts/yaml-validator.js +0 -397
  80. package/.sinapse-ai/hooks/sinapse-brand-grounding.cjs +0 -162
  81. package/.sinapse-ai/hooks/sinapse-ds-grounding.cjs +0 -211
  82. package/.sinapse-ai/hooks/sinapse-vault-grounding.cjs +0 -194
  83. package/bin/lib/register-grounding-hooks.js +0 -145
  84. package/docs/guides/agents/traces/execution-traces.zip +0 -0
  85. package/docs/guides/grounding-setup.md +0 -154
  86. package/docs/guides/workflows/SINAPSE-WORKFLOWS.zip +0 -0
  87. package/packages/installer/src/pro/pro-scaffolder.js +0 -449
  88. package/packages/installer/src/wizard/grounding-config.js +0 -131
  89. package/packages/installer/src/wizard/pro-setup.js +0 -1439
  90. package/packages/installer/templates/README.md +0 -16
  91. package/packages/installer/templates/brand-routing.example.json +0 -12
  92. package/packages/installer/templates/ds-routing.example.json +0 -12
  93. package/packages/installer/templates/vault-routing.example.json +0 -12
@@ -1,154 +0,0 @@
1
- # Grounding Setup Guide
2
-
3
- > Story 10.47 — opt-in BYO (Bring Your Own) vault, design system, brand.
4
-
5
- ## What is grounding?
6
-
7
- Most agent frameworks assume the LLM already knows everything about your
8
- business. SINAPSE-AI assumes the opposite: it ships three optional
9
- **grounding** channels that connect agents to your real context.
10
-
11
- | Channel | Purpose | Disabled = |
12
- |---------|---------|------------|
13
- | **Vault** | Inject context from your markdown notes vault into agent prompts | Hook is a no-op |
14
- | **Design System** | Resolve tokens / components / layouts from your DS source-of-truth so agents stop generating generic `max-w-7xl shadcn-default` UIs | Hook is a no-op (high-quality fallback principles still apply at the framework level) |
15
- | **Brand** | Read your brandbook (positioning, MVV, tone-of-voice) so copy and UX nasce aligned to your brand | Hook is a no-op |
16
-
17
- All three are independent. Configure the ones that matter, skip the rest.
18
-
19
- ## When does each one make sense?
20
-
21
- - **Enable vault grounding** if you keep a structured markdown knowledge base with notes about your projects, clients, decisions, or domain. Agents reference these instead of inventing.
22
- - **Enable design system grounding** if your project has a defined visual identity (tokens, components, layout primitives). Without it, design agents fall back to generic principles — still good, but not yours.
23
- - **Enable brand grounding** if you have a brandbook with positioning, MVV, tone-of-voice. Especially valuable for copy + storytelling squads.
24
- - **Skip all three** if you're prototyping or evaluating. The framework defaults are high-quality enough to ship; you can come back later via `--reconfigure`.
25
-
26
- ## How to configure
27
-
28
- ### Option A — interactive wizard (recommended)
29
-
30
- ```bash
31
- npx sinapse-ai install # first run, prompts every section
32
- npx sinapse-ai install --reconfigure # re-run prompts on an existing install
33
- ```
34
-
35
- The wizard asks one question per section. Each accepts a path or empty
36
- (skip). Empty answers leave the section disabled and the corresponding
37
- shipped hook stays a complete no-op.
38
-
39
- ### Option B — manual edit
40
-
41
- The wizard writes to `~/.claude/sinapse-ai-config.yaml`. You can create or
42
- edit this file directly using the schema below.
43
-
44
- ```yaml
45
- version: "1"
46
- grounding:
47
- vault:
48
- enabled: true
49
- path: "/absolute/path/to/your/vault"
50
- domains: [] # optional: list of domain tags to filter on
51
- designSystem:
52
- enabled: true
53
- profileName: "my-product-ds"
54
- rootPath: "/absolute/path/to/your/design-system"
55
- brand:
56
- enabled: false
57
- profileName: ""
58
- brandbookPath: ""
59
- ```
60
-
61
- Generic JSON examples for copy-paste reference live in
62
- `packages/installer/templates/`:
63
-
64
- - `vault-routing.example.json`
65
- - `ds-routing.example.json`
66
- - `brand-routing.example.json`
67
-
68
- ## How fallback works
69
-
70
- When a section is disabled (or the YAML file is absent altogether), the
71
- shipped grounding hook for that section returns `null`. Agents proceed
72
- without that channel of context. There are **no warnings, no errors, no
73
- log noise** — only silence.
74
-
75
- Design system grounding has an additional safety net: even with the hook
76
- disabled, the framework's design-system rules still apply
77
- high-quality fallback principles (no hardcoded `max-w-7xl`, fluid
78
- typography, semantic tokens) so generated UIs do not collapse into
79
- generic SaaS templates.
80
-
81
- ## Schema versioning
82
-
83
- The top-level `version` field allows future migrations. Always check
84
- `~/.claude/sinapse-ai-config.yaml` after a major SINAPSE-AI upgrade. If
85
- the schema bumps, the installer will migrate it on the next run; manual
86
- edits should preserve unknown keys.
87
-
88
- ## Privacy
89
-
90
- Paths configured here stay local on your machine. SINAPSE-AI does not
91
- ship vault contents anywhere. The grounding hooks read locally and
92
- inject only what the agent's working context requires for the task at
93
- hand.
94
-
95
- ## How the hooks work (Story GA-1.6)
96
-
97
- After running `sinapse-ai install` (or `update`), three executable hooks
98
- are placed in `~/.sinapse/hooks/` and registered in
99
- `~/.claude/settings.json` under `UserPromptSubmit`:
100
-
101
- | Hook | Reads from config | Activates when | Injects |
102
- |------|-------------------|---------------|---------|
103
- | `sinapse-vault-grounding.cjs` | `grounding.vault` | Any prompt ≥ 10 chars | `<vault-grounding>` with up to 5 most-recent vault notes (500 chars each, 6000 total cap) |
104
- | `sinapse-ds-grounding.cjs` | `grounding.designSystem` | Prompt contains UI keywords (PT + EN: `pagina`, `componente`, `layout`, `react`, `tailwind`, etc.) | `<ds-grounding>` with the first 3000 chars of the DS law file (`0.0-guidelines.md`, `principles.md`, `README.md`, …) |
105
- | `sinapse-brand-grounding.cjs` | `grounding.brand` | Any prompt ≥ 10 chars | `<brand-grounding>` with the first 2000 chars of the brandbook |
106
-
107
- All three follow the same contract:
108
-
109
- - **Fail-open** — any error (missing config, malformed YAML, unreadable
110
- file, timeout) exits 0 silently. Claude Code never sees a broken
111
- prompt because grounding misfired.
112
- - **Anti-double-injection** — if the prompt already contains the
113
- matching tag (e.g. another tool or a previous hook ran), the hook is
114
- a no-op.
115
- - **Timeout 3500 ms** — each hook short-circuits on its own timer so a
116
- slow filesystem can't stall agent activation.
117
- - **Size caps** — vault 6000 / DS 3000 / brand 2000 chars. Truncated
118
- with a `[...truncated...]` marker.
119
-
120
- ### Coexistence with personal hooks
121
-
122
- If you also maintain personal global hooks (e.g.
123
- `~/.claude/hooks/vault-grounding.cjs` reading from
124
- `~/.claude/vault-routing.json`), the framework hooks are
125
- **file-name-prefixed** with `sinapse-` and **read a different config
126
- file** (`~/.claude/sinapse-ai-config.yaml`). Both can run side-by-side
127
- without conflict — the fail-open guarantee makes any duplication
128
- harmless.
129
-
130
- ### Disabling temporarily
131
-
132
- Open `~/.claude/sinapse-ai-config.yaml` and set the section's
133
- `enabled: false`. Re-running `sinapse-ai install --reconfigure` lets you
134
- update paths interactively.
135
-
136
- ## What grounding does NOT do (today)
137
-
138
- Story `10.47` shipped the **foundation** (schema, wizard, no-op library
139
- hooks). Story `GA-1.6` (this entry) ships **executable hooks** that
140
- inject real context. The concrete domain integration logic — parsing
141
- note frontmatter, walking a deep DS token tree, reading a
142
- brandbook PDF — still lands in follow-up stories per channel.
143
-
144
- ## Troubleshooting
145
-
146
- | Symptom | Cause | Fix |
147
- |---------|-------|-----|
148
- | "Vault grounding configured" message but no notes injected | Concrete parser not yet shipped (out-of-scope for 10.47) | Wait for follow-up story, or open an issue if blocking |
149
- | Hook errors during agent run | Should not happen — hooks short-circuit on any error | Check `~/.claude/sinapse-ai-config.yaml` is valid YAML; report issue if persistent |
150
- | Wizard does not re-ask grounding questions | Upsert mode — already configured | Run with `--reconfigure` to re-prompt |
151
-
152
- ---
153
-
154
- *See also:* [`README.md`](../../README.md), [`packages/installer/templates/README.md`](../../packages/installer/templates/README.md).
@@ -1,449 +0,0 @@
1
- /**
2
- * Pro Content Scaffolder
3
- *
4
- * Copies premium content (squads, configs, feature registry) from
5
- * node_modules/@sinapse-fullstack/pro/ into the user's project after
6
- * license activation.
7
- *
8
- * @module packages/installer/src/pro/pro-scaffolder
9
- * @story INS-3.1 — Implement Pro Content Scaffolder
10
- */
11
-
12
- 'use strict';
13
-
14
- const fs = require('fs-extra');
15
- const path = require('path');
16
- const yaml = require('js-yaml');
17
- const { hashFileAsync, hashFilesMatchAsync } = require('../installer/file-hasher');
18
-
19
- /**
20
- * Directories excluded from scaffolding (private/internal squads).
21
- */
22
- const SCAFFOLD_EXCLUDES = [
23
- 'mmos-squad',
24
- ];
25
-
26
- /**
27
- * Items to scaffold from pro package into user project.
28
- * Each entry defines source (relative to proSourceDir) and dest (relative to targetDir).
29
- */
30
- const SCAFFOLD_ITEMS = [
31
- {
32
- type: 'directory',
33
- source: 'squads',
34
- dest: 'squads',
35
- description: 'Pro squads',
36
- required: true,
37
- },
38
- {
39
- type: 'file',
40
- source: 'pro-config.yaml',
41
- dest: path.join('.sinapse-ai', 'pro-config.yaml'),
42
- description: 'Pro configuration',
43
- required: true,
44
- },
45
- {
46
- type: 'file',
47
- source: 'feature-registry.yaml',
48
- dest: path.join('.sinapse-ai', 'feature-registry.yaml'),
49
- description: 'Feature registry',
50
- required: false,
51
- },
52
- ];
53
-
54
- /**
55
- * Scaffold pro content into user project.
56
- *
57
- * @param {string} targetDir - Project root directory
58
- * @param {string} proSourceDir - Path to pro package content (node_modules/@sinapse-fullstack/pro)
59
- * @param {Object} [options={}] - Scaffold options
60
- * @param {Function} [options.onProgress] - Progress callback ({item, status, message})
61
- * @param {boolean} [options.force=false] - Force overwrite even if content exists
62
- * @returns {Promise<Object>} Scaffold result with copiedFiles, warnings, manifest
63
- */
64
- async function scaffoldProContent(targetDir, proSourceDir, options = {}) {
65
- const { onProgress = null, force = false } = options;
66
-
67
- const result = {
68
- success: false,
69
- copiedFiles: [],
70
- skippedFiles: [],
71
- warnings: [],
72
- errors: [],
73
- manifest: null,
74
- versionInfo: null,
75
- };
76
-
77
- // Track files for rollback on partial failure
78
- const rollbackFiles = [];
79
-
80
- // Validate pro source exists
81
- if (!await fs.pathExists(proSourceDir)) {
82
- result.errors.push(
83
- `Pro package not found at ${proSourceDir}. Run "npm install @sinapse-fullstack/pro" first.`,
84
- );
85
- return result;
86
- }
87
-
88
- try {
89
- for (const item of SCAFFOLD_ITEMS) {
90
- const sourcePath = path.join(proSourceDir, item.source);
91
- const destPath = path.join(targetDir, item.dest);
92
-
93
- // Check source exists
94
- if (!await fs.pathExists(sourcePath)) {
95
- if (item.required) {
96
- throw new Error(`Required pro content not found: ${item.source}`);
97
- }
98
- const warning = `${item.description} (${item.source}) not found in pro package — skipping`;
99
- result.warnings.push(warning);
100
- if (onProgress) {
101
- onProgress({ item: item.source, status: 'warning', message: warning });
102
- }
103
- continue;
104
- }
105
-
106
- if (item.type === 'directory') {
107
- const copied = await scaffoldDirectory(sourcePath, destPath, { force, rollbackFiles, baseDir: targetDir });
108
- result.copiedFiles.push(...copied.copiedFiles);
109
- result.skippedFiles.push(...copied.skippedFiles);
110
- } else {
111
- const copied = await scaffoldFile(sourcePath, destPath, { force, rollbackFiles, baseDir: targetDir });
112
- if (copied.skipped) {
113
- result.skippedFiles.push(copied.relativePath);
114
- } else {
115
- result.copiedFiles.push(copied.relativePath);
116
- }
117
- }
118
-
119
- if (onProgress) {
120
- onProgress({ item: item.source, status: 'done', message: `${item.description} scaffolded` });
121
- }
122
- }
123
-
124
- // Merge pro-config into core-config
125
- const merged = await mergeProConfig(targetDir);
126
- if (merged && onProgress) {
127
- onProgress({ item: 'pro-config', status: 'done', message: 'Pro config merged into core-config.yaml' });
128
- }
129
-
130
- // Install squad agent commands to IDEs
131
- const commandsResult = await installSquadCommands(targetDir);
132
- if (commandsResult.installed > 0) {
133
- result.copiedFiles.push(...commandsResult.files);
134
- if (onProgress) {
135
- onProgress({ item: 'squad-setup', status: 'done',
136
- message: `${commandsResult.installed} squad agent commands installed` });
137
- }
138
- }
139
-
140
- // Generate pro-version.json (AC4)
141
- const versionInfo = await generateProVersionJson(targetDir, proSourceDir, result.copiedFiles);
142
- result.versionInfo = versionInfo;
143
- result.copiedFiles.push('pro-version.json');
144
- rollbackFiles.push(path.join(targetDir, 'pro-version.json'));
145
-
146
- // Generate pro-installed-manifest.yaml (AC8)
147
- const manifest = await generateInstalledManifest(targetDir, result.copiedFiles);
148
- result.manifest = manifest;
149
- result.copiedFiles.push('pro-installed-manifest.yaml');
150
- rollbackFiles.push(path.join(targetDir, 'pro-installed-manifest.yaml'));
151
-
152
- result.success = true;
153
-
154
- } catch (error) {
155
- result.errors.push(error.message);
156
-
157
- // Rollback partially copied files (AC6)
158
- const rollbackResult = await rollbackScaffold(rollbackFiles);
159
- if (rollbackResult.errors.length > 0) {
160
- result.errors.push(`Rollback errors: ${rollbackResult.errors.join(', ')}`);
161
- }
162
- result.warnings.push(
163
- `Scaffolding failed: ${error.message}. ${rollbackResult.removed} files cleaned up.`,
164
- );
165
- }
166
-
167
- return result;
168
- }
169
-
170
- /**
171
- * Scaffold a directory recursively with idempotency checks.
172
- *
173
- * @param {string} sourceDir - Source directory
174
- * @param {string} destDir - Destination directory
175
- * @param {Object} options - Options
176
- * @returns {Promise<Object>} Result with copiedFiles and skippedFiles
177
- */
178
- async function scaffoldDirectory(sourceDir, destDir, options = {}) {
179
- const { force = false, rollbackFiles = [], baseDir } = options;
180
- const copiedFiles = [];
181
- const skippedFiles = [];
182
-
183
- await fs.ensureDir(destDir);
184
-
185
- const items = await fs.readdir(sourceDir, { withFileTypes: true });
186
-
187
- for (const item of items) {
188
- // Skip excluded directories (e.g. private squads)
189
- if (SCAFFOLD_EXCLUDES.includes(item.name)) {
190
- continue;
191
- }
192
-
193
- const sourcePath = path.join(sourceDir, item.name);
194
- const destPath = path.join(destDir, item.name);
195
-
196
- if (item.isDirectory()) {
197
- const sub = await scaffoldDirectory(sourcePath, destPath, options);
198
- copiedFiles.push(...sub.copiedFiles);
199
- skippedFiles.push(...sub.skippedFiles);
200
- } else {
201
- const result = await scaffoldFile(sourcePath, destPath, { force, rollbackFiles, baseDir });
202
- if (result.skipped) {
203
- skippedFiles.push(result.relativePath);
204
- } else {
205
- copiedFiles.push(result.relativePath);
206
- }
207
- }
208
- }
209
-
210
- return { copiedFiles, skippedFiles };
211
- }
212
-
213
- /**
214
- * Scaffold a single file with idempotency (AC5).
215
- * If dest exists and has identical hash, skip. If user modified, skip (preserve).
216
- *
217
- * @param {string} sourcePath - Source file path
218
- * @param {string} destPath - Destination file path
219
- * @param {Object} options - Options
220
- * @returns {Promise<Object>} Result with relativePath and skipped flag
221
- */
222
- async function scaffoldFile(sourcePath, destPath, options = {}) {
223
- const { force = false, rollbackFiles = [], baseDir } = options;
224
- const base = baseDir || path.resolve(destPath, '..', '..');
225
- const relativePath = path.relative(base, destPath).replace(/\\/g, '/');
226
-
227
- // Idempotency check (AC5) — async to avoid blocking event loop
228
- if (!force && await fs.pathExists(destPath)) {
229
- try {
230
- if (await hashFilesMatchAsync(sourcePath, destPath)) {
231
- // Identical — skip
232
- return { relativePath, skipped: true };
233
- }
234
- } catch {
235
- // Hash comparison failed — overwrite to be safe
236
- }
237
- }
238
-
239
- await fs.ensureDir(path.dirname(destPath));
240
- await fs.copy(sourcePath, destPath);
241
- rollbackFiles.push(destPath);
242
-
243
- return { relativePath, skipped: false };
244
- }
245
-
246
- /**
247
- * Generate pro-version.json with SHA256 hashes for version tracking (AC4).
248
- *
249
- * @param {string} targetDir - Project root
250
- * @param {string} proSourceDir - Pro package directory
251
- * @param {string[]} copiedFiles - List of copied file relative paths
252
- * @returns {Promise<Object>} Version info object
253
- */
254
- async function generateProVersionJson(targetDir, proSourceDir, copiedFiles) {
255
- // Read pro package version
256
- let proVersion = 'unknown';
257
- const proPkgPath = path.join(proSourceDir, 'package.json');
258
- if (await fs.pathExists(proPkgPath)) {
259
- try {
260
- const proPkg = await fs.readJson(proPkgPath);
261
- proVersion = proPkg.version || 'unknown';
262
- } catch {
263
- // Keep 'unknown'
264
- }
265
- }
266
-
267
- // Generate hashes for all copied files
268
- const fileHashes = {};
269
- for (const relativePath of copiedFiles) {
270
- const absolutePath = path.join(targetDir, relativePath);
271
- try {
272
- if (await fs.pathExists(absolutePath)) {
273
- const stats = await fs.stat(absolutePath);
274
- if (stats.isFile()) {
275
- fileHashes[relativePath] = `sha256:${await hashFileAsync(absolutePath)}`;
276
- }
277
- }
278
- } catch {
279
- // Skip unhashable files
280
- }
281
- }
282
-
283
- const versionInfo = {
284
- proVersion,
285
- installedAt: new Date().toISOString(),
286
- fileCount: copiedFiles.length,
287
- fileHashes,
288
- };
289
-
290
- const versionPath = path.join(targetDir, 'pro-version.json');
291
- await fs.writeJson(versionPath, versionInfo, { spaces: 2 });
292
-
293
- return versionInfo;
294
- }
295
-
296
- /**
297
- * Generate pro-installed-manifest.yaml listing all scaffolded files (AC8).
298
- *
299
- * @param {string} targetDir - Project root
300
- * @param {string[]} copiedFiles - List of copied file relative paths
301
- * @returns {Promise<Object>} Manifest object
302
- */
303
- async function generateInstalledManifest(targetDir, copiedFiles) {
304
- const files = [];
305
- for (const relativePath of copiedFiles) {
306
- const absolutePath = path.join(targetDir, relativePath);
307
- let timestamp = new Date().toISOString();
308
- try {
309
- if (await fs.pathExists(absolutePath)) {
310
- const stats = await fs.stat(absolutePath);
311
- timestamp = stats.mtime.toISOString();
312
- }
313
- } catch {
314
- // Use current time
315
- }
316
- files.push({ path: relativePath, timestamp });
317
- }
318
-
319
- const manifest = {
320
- generatedAt: new Date().toISOString(),
321
- totalFiles: files.length,
322
- files,
323
- };
324
-
325
- const manifestPath = path.join(targetDir, 'pro-installed-manifest.yaml');
326
- await fs.writeFile(manifestPath, yaml.dump(manifest), 'utf8');
327
-
328
- return manifest;
329
- }
330
-
331
- /**
332
- * Rollback partially scaffolded files on error (AC6).
333
- *
334
- * @param {string[]} rollbackFiles - Absolute paths to remove
335
- * @returns {Promise<Object>} Rollback result with removed count and errors
336
- */
337
- async function rollbackScaffold(rollbackFiles) {
338
- let removed = 0;
339
- const errors = [];
340
-
341
- for (const filePath of rollbackFiles) {
342
- try {
343
- if (await fs.pathExists(filePath)) {
344
- await fs.remove(filePath);
345
- removed++;
346
- }
347
- } catch (error) {
348
- errors.push(`Failed to remove ${filePath}: ${error.message}`);
349
- }
350
- }
351
-
352
- return { removed, errors };
353
- }
354
-
355
- /**
356
- * Merge pro-config.yaml sections into core-config.yaml.
357
- * Deep merges top-level keys (pro, memory, metrics, integrations, squads).
358
- *
359
- * @param {string} targetDir - Project root directory
360
- * @returns {Promise<boolean>} True if merge was performed
361
- */
362
- async function mergeProConfig(targetDir) {
363
- const coreConfigPath = path.join(targetDir, '.sinapse-ai', 'core-config.yaml');
364
- const proConfigPath = path.join(targetDir, '.sinapse-ai', 'pro-config.yaml');
365
-
366
- if (!await fs.pathExists(proConfigPath) || !await fs.pathExists(coreConfigPath)) {
367
- return false;
368
- }
369
-
370
- const coreConfig = yaml.load(await fs.readFile(coreConfigPath, 'utf8')) || {};
371
- const proConfig = yaml.load(await fs.readFile(proConfigPath, 'utf8')) || {};
372
-
373
- for (const [key, value] of Object.entries(proConfig)) {
374
- if (coreConfig[key] && typeof coreConfig[key] === 'object' && typeof value === 'object' && !Array.isArray(value)) {
375
- coreConfig[key] = { ...coreConfig[key], ...value };
376
- } else {
377
- coreConfig[key] = value;
378
- }
379
- }
380
-
381
- await fs.writeFile(coreConfigPath, yaml.dump(coreConfig, { lineWidth: -1 }), 'utf8');
382
- return true;
383
- }
384
-
385
- /**
386
- * Install squad agent commands into active IDE directories.
387
- * Detects which IDEs are configured and copies agent .md files accordingly.
388
- *
389
- * @param {string} targetDir - Project root directory
390
- * @returns {Promise<Object>} Result with installed count and file list
391
- */
392
- async function installSquadCommands(targetDir) {
393
- const squadsDir = path.join(targetDir, 'squads');
394
- if (!await fs.pathExists(squadsDir)) return { installed: 0, files: [] };
395
-
396
- const ideTargets = [
397
- { check: path.join('.claude', 'commands'), dest: (squad) => path.join('.claude', 'commands', squad) },
398
- { check: path.join('.codex', 'agents'), dest: () => path.join('.codex', 'agents') },
399
- { check: path.join('.gemini', 'rules'), dest: (squad) => path.join('.gemini', 'rules', squad) },
400
- { check: path.join('.cursor', 'rules'), dest: () => path.join('.cursor', 'rules') },
401
- ];
402
-
403
- const activeIDEs = [];
404
- for (const ide of ideTargets) {
405
- if (await fs.pathExists(path.join(targetDir, ide.check))) {
406
- activeIDEs.push(ide);
407
- }
408
- }
409
- if (activeIDEs.length === 0) return { installed: 0, files: [] };
410
-
411
- const files = [];
412
- const items = await fs.readdir(squadsDir, { withFileTypes: true });
413
-
414
- for (const item of items) {
415
- if (!item.isDirectory()) continue;
416
- const agentsDir = path.join(squadsDir, item.name, 'agents');
417
- if (!await fs.pathExists(agentsDir)) continue;
418
-
419
- const agentFiles = (await fs.readdir(agentsDir))
420
- .filter(f => f.endsWith('.md') && !f.startsWith('test-'));
421
-
422
- for (const ide of activeIDEs) {
423
- const destDir = path.join(targetDir, ide.dest(item.name));
424
- await fs.ensureDir(destDir);
425
- for (const agentFile of agentFiles) {
426
- await fs.copy(
427
- path.join(agentsDir, agentFile),
428
- path.join(destDir, agentFile),
429
- );
430
- files.push(path.relative(targetDir, path.join(destDir, agentFile)).replace(/\\/g, '/'));
431
- }
432
- }
433
- }
434
-
435
- return { installed: files.length, files };
436
- }
437
-
438
- module.exports = {
439
- scaffoldProContent,
440
- scaffoldDirectory,
441
- scaffoldFile,
442
- generateProVersionJson,
443
- generateInstalledManifest,
444
- rollbackScaffold,
445
- mergeProConfig,
446
- installSquadCommands,
447
- SCAFFOLD_ITEMS,
448
- SCAFFOLD_EXCLUDES,
449
- };