sinapse-ai 1.6.1 → 1.8.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 (131) hide show
  1. package/.claude/CLAUDE.md +5 -11
  2. package/.claude/hooks/README.md +14 -1
  3. package/.claude/hooks/code-intel-pretool.cjs +115 -0
  4. package/.claude/hooks/enforce-delegation.cjs +31 -3
  5. package/.claude/hooks/enforce-framework-boundary.cjs +324 -0
  6. package/.claude/hooks/enforce-permission-mode.cjs +249 -0
  7. package/.claude/hooks/secret-scanning.cjs +34 -43
  8. package/.claude/hooks/synapse-engine.cjs +23 -23
  9. package/.claude/hooks/telemetry-post-tool.cjs +128 -0
  10. package/.claude/hooks/telemetry-stop.cjs +132 -0
  11. package/.claude/hooks/verify-packages.cjs +9 -2
  12. package/.claude/rules/documentation-first.md +1 -1
  13. package/.claude/rules/hook-governance.md +2 -0
  14. package/.sinapse-ai/cli/commands/health/index.js +24 -0
  15. package/.sinapse-ai/core/README.md +11 -0
  16. package/.sinapse-ai/core/config/config-loader.js +19 -0
  17. package/.sinapse-ai/core/config/merge-utils.js +8 -0
  18. package/.sinapse-ai/core/errors/constants.js +147 -0
  19. package/.sinapse-ai/core/errors/error-registry.js +176 -0
  20. package/.sinapse-ai/core/errors/index.js +50 -0
  21. package/.sinapse-ai/core/errors/serializer.js +147 -0
  22. package/.sinapse-ai/core/errors/sinapse-error.js +144 -0
  23. package/.sinapse-ai/core/errors/utils.js +187 -0
  24. package/.sinapse-ai/core/execution/build-orchestrator.js +47 -49
  25. package/.sinapse-ai/core/execution/build-state-manager.js +183 -31
  26. package/.sinapse-ai/core/execution/parallel-executor.js +7 -1
  27. package/.sinapse-ai/core/execution/semantic-merge-engine.js +26 -14
  28. package/.sinapse-ai/core/execution/subagent-dispatcher.js +201 -60
  29. package/.sinapse-ai/core/execution/wave-executor.js +4 -1
  30. package/.sinapse-ai/core/grounding/README.md +71 -11
  31. package/.sinapse-ai/core/health-check/checks/project/framework-config.js +38 -2
  32. package/.sinapse-ai/core/health-check/checks/project/package-json.js +47 -3
  33. package/.sinapse-ai/core/health-check/checks/services/gemini-cli.js +117 -0
  34. package/.sinapse-ai/core/health-check/checks/services/index.js +2 -0
  35. package/.sinapse-ai/core/health-check/healers/index.js +40 -3
  36. package/.sinapse-ai/core/ideation/ideation-engine.js +212 -107
  37. package/.sinapse-ai/core/ids/gate-evaluator.js +318 -0
  38. package/.sinapse-ai/core/ids/gates/g5-semantic-handshake.js +190 -0
  39. package/.sinapse-ai/core/ids/gates/g6-ci-integrity.js +162 -0
  40. package/.sinapse-ai/core/ids/index.js +30 -0
  41. package/.sinapse-ai/core/memory/__tests__/active-modules.verify.js +11 -0
  42. package/.sinapse-ai/core/memory/gotchas-memory.js +37 -2
  43. package/.sinapse-ai/core/orchestration/agent-invoker.js +29 -6
  44. package/.sinapse-ai/core/orchestration/brownfield-handler.js +36 -3
  45. package/.sinapse-ai/core/orchestration/condition-evaluator.js +57 -0
  46. package/.sinapse-ai/core/orchestration/executors/epic-3-executor.js +76 -5
  47. package/.sinapse-ai/core/orchestration/executors/epic-4-executor.js +63 -17
  48. package/.sinapse-ai/core/orchestration/executors/epic-6-executor.js +153 -41
  49. package/.sinapse-ai/core/orchestration/executors/epic-executor.js +40 -0
  50. package/.sinapse-ai/core/orchestration/greenfield-handler.js +87 -3
  51. package/.sinapse-ai/core/orchestration/master-orchestrator.js +150 -10
  52. package/.sinapse-ai/core/orchestration/parallel-executor.js +6 -1
  53. package/.sinapse-ai/core/orchestration/recovery-handler.js +81 -8
  54. package/.sinapse-ai/core/orchestration/workflow-executor.js +41 -0
  55. package/.sinapse-ai/core/registry/registry-loader.js +71 -5
  56. package/.sinapse-ai/core/registry/squad-agent-resolver.js +253 -0
  57. package/.sinapse-ai/core/synapse/context/context-tracker.js +104 -9
  58. package/.sinapse-ai/core/synapse/context/index.js +19 -0
  59. package/.sinapse-ai/core/synapse/context/semantic-handshake-engine.js +555 -0
  60. package/.sinapse-ai/core/synapse/diagnostics/collectors/pipeline-collector.js +4 -2
  61. package/.sinapse-ai/core/synapse/engine.js +43 -3
  62. package/.sinapse-ai/core/telemetry/ids-sink.js +188 -0
  63. package/.sinapse-ai/core/utils/output-formatter.js +8 -290
  64. package/.sinapse-ai/core/utils/spawn-safe.js +186 -0
  65. package/.sinapse-ai/core-config.yaml +68 -1
  66. package/.sinapse-ai/data/entity-registry.yaml +15082 -13618
  67. package/.sinapse-ai/data/registry-update-log.jsonl +143 -0
  68. package/.sinapse-ai/development/agents/developer.md +2 -0
  69. package/.sinapse-ai/development/agents/devops.md +9 -0
  70. package/.sinapse-ai/development/external-executors/README.md +18 -0
  71. package/.sinapse-ai/development/external-executors/codex.md +56 -0
  72. package/.sinapse-ai/development/scripts/populate-entity-registry.js +65 -9
  73. package/.sinapse-ai/development/scripts/squad/squad-downloader.js +169 -14
  74. package/.sinapse-ai/development/tasks/delegate-to-external-executor.md +152 -0
  75. package/.sinapse-ai/development/tasks/github-devops-pre-push-quality-gate.md +46 -29
  76. package/.sinapse-ai/development/tasks/update-sinapse.md +3 -3
  77. package/.sinapse-ai/hooks/sinapse-brand-grounding.cjs +4 -7
  78. package/.sinapse-ai/hooks/sinapse-ds-grounding.cjs +5 -8
  79. package/.sinapse-ai/hooks/sinapse-vault-grounding.cjs +6 -9
  80. package/.sinapse-ai/infrastructure/integrations/ai-providers/ai-provider-factory.js +4 -1
  81. package/.sinapse-ai/infrastructure/integrations/ai-providers/claude-provider.js +57 -55
  82. package/.sinapse-ai/infrastructure/integrations/pm-adapters/github-adapter.js +9 -7
  83. package/.sinapse-ai/infrastructure/scripts/ide-sync/gemini-commands.js +298 -0
  84. package/.sinapse-ai/infrastructure/scripts/ide-sync/index.js +127 -6
  85. package/.sinapse-ai/infrastructure/scripts/ide-sync/persona-renderer.js +97 -0
  86. package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/antigravity.js +121 -0
  87. package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/cursor.js +119 -0
  88. package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/github-copilot.js +191 -0
  89. package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/kimi.js +448 -0
  90. package/.sinapse-ai/install-manifest.yaml +218 -114
  91. package/.sinapse-ai/product/templates/engine/renderer.js +20 -1
  92. package/.sinapse-ai/scripts/pm.sh +18 -6
  93. package/bin/cli.js +17 -0
  94. package/bin/commands/agents.js +96 -0
  95. package/bin/commands/doctor.js +15 -0
  96. package/bin/commands/ideate.js +129 -0
  97. package/bin/commands/uninstall.js +40 -0
  98. package/bin/postinstall.js +50 -4
  99. package/bin/sinapse.js +146 -2
  100. package/bin/utils/secret-scanner-core.js +253 -0
  101. package/bin/utils/staged-secret-scan.js +106 -40
  102. package/docs/framework/collaboration-autonomy-plan.md +18 -18
  103. package/docs/guides/parallel-workflow.md +6 -6
  104. package/package.json +22 -5
  105. package/packages/installer/src/installer/git-hooks-installer.js +384 -0
  106. package/packages/installer/src/installer/sinapse-ai-installer.js +16 -0
  107. package/packages/installer/src/wizard/ide-config-generator.js +23 -0
  108. package/packages/installer/src/wizard/validators.js +38 -1
  109. package/packages/installer/tests/unit/artifact-copy-pipeline/artifact-copy-pipeline.test.js +5 -1
  110. package/packages/installer/tests/unit/doctor/doctor-checks.test.js +44 -22
  111. package/packages/installer/tests/unit/git-hooks-installer.test.js +262 -0
  112. package/scripts/eval-runner.js +422 -0
  113. package/scripts/generate-install-manifest.js +13 -9
  114. package/scripts/generate-synapse-runtime.js +51 -0
  115. package/scripts/regenerate-orqx-stubs.ps1 +6 -5
  116. package/scripts/validate-all.js +1 -0
  117. package/scripts/validate-evals.js +466 -0
  118. package/scripts/validate-schemas.js +539 -0
  119. package/scripts/validate-squad-orqx.js +9 -2
  120. package/squads/claude-code-mastery/knowledge-base/memory-systems-reference.md +1 -1
  121. package/squads/squad-brand/templates/client-delivery-template.md +1 -1
  122. package/squads/squad-content/knowledge-base/social-compression-framework.md +1 -1
  123. package/squads/squad-council/knowledge-base/brand-strategy-models.md +1 -1
  124. package/.sinapse-ai/development/scripts/elicitation-engine.js +0 -385
  125. package/.sinapse-ai/development/scripts/elicitation-session-manager.js +0 -300
  126. package/.sinapse-ai/development/tasks/test-validation-task.md +0 -172
  127. package/docs/chrome-brain-upgrade-plan.md +0 -624
  128. package/docs/constitution-compliance.md +0 -87
  129. package/docs/mega-upgrade-orchestration-plan.md +0 -71
  130. package/docs/research-synthesis-for-upgrade.md +0 -511
  131. package/docs/security-audit-report.md +0 -306
@@ -33,6 +33,39 @@ const GITHUB_API_BASE =
33
33
  */
34
34
  const DEFAULT_SQUADS_PATH = './squads';
35
35
 
36
+ /**
37
+ * Allowlist of HTTPS hosts permitted when following HTTP redirects.
38
+ * Prevents SSRF / redirect-based exfiltration to arbitrary hosts.
39
+ * @constant {Set<string>}
40
+ */
41
+ const ALLOWED_REDIRECT_HOSTS = new Set([
42
+ 'raw.githubusercontent.com',
43
+ 'api.github.com',
44
+ 'github.com',
45
+ 'objects.githubusercontent.com',
46
+ 'codeload.github.com',
47
+ ]);
48
+
49
+ /**
50
+ * Maximum number of redirects to follow before aborting a fetch.
51
+ * @constant {number}
52
+ */
53
+ const MAX_REDIRECTS = 5;
54
+
55
+ /**
56
+ * Per-request timeout (ms). Aborts a hung/slow remote so a compromised or
57
+ * unresponsive host can't stall the installer indefinitely (P3-001).
58
+ * @constant {number}
59
+ */
60
+ const REQUEST_TIMEOUT_MS = 30000;
61
+
62
+ /**
63
+ * Maximum response size (bytes). Caps a single download so a malicious/
64
+ * misconfigured host can't exhaust memory (10 MB is ample for a squad).
65
+ * @constant {number}
66
+ */
67
+ const MAX_RESPONSE_BYTES = 10 * 1024 * 1024;
68
+
36
69
  /**
37
70
  * Error codes for SquadDownloaderError
38
71
  * @enum {string}
@@ -370,6 +403,51 @@ class SquadDownloader {
370
403
  this._log(`Downloaded ${contents.length} items to ${targetPath}`);
371
404
  }
372
405
 
406
+ /**
407
+ * Resolve and validate that an item lands strictly inside the target path.
408
+ * Defends against zip-slip / path traversal when `item.name` originates from
409
+ * an untrusted remote source (GitHub API / registry).
410
+ *
411
+ * @private
412
+ * @param {string} targetPath - Trusted base directory
413
+ * @param {string} itemName - Untrusted entry name from remote response
414
+ * @returns {string} Safe, resolved item path contained within targetPath
415
+ * @throws {SquadDownloaderError} VALIDATION_ERROR if traversal is detected
416
+ */
417
+ _safeResolve(targetPath, itemName) {
418
+ // Reject names that are missing, contain path separators, or use '..'.
419
+ if (
420
+ typeof itemName !== 'string' ||
421
+ itemName.length === 0 ||
422
+ itemName === '.' ||
423
+ itemName === '..' ||
424
+ itemName.includes('/') ||
425
+ itemName.includes('\\') ||
426
+ itemName.includes('\0')
427
+ ) {
428
+ throw new SquadDownloaderError(
429
+ DownloaderErrorCodes.VALIDATION_ERROR,
430
+ `Unsafe item name from remote response: "${itemName}"`,
431
+ 'Squad entries must be plain file/directory names without path separators or ".."',
432
+ );
433
+ }
434
+
435
+ const itemPath = path.join(targetPath, itemName);
436
+ const resolved = path.resolve(itemPath);
437
+ const base = path.resolve(targetPath);
438
+
439
+ // Resolved path must be strictly inside the base directory.
440
+ if (resolved !== base && !resolved.startsWith(base + path.sep)) {
441
+ throw new SquadDownloaderError(
442
+ DownloaderErrorCodes.VALIDATION_ERROR,
443
+ `Path traversal detected: "${itemName}" escapes target directory`,
444
+ 'Squad entries must stay within the download directory',
445
+ );
446
+ }
447
+
448
+ return itemPath;
449
+ }
450
+
373
451
  /**
374
452
  * Download contents recursively
375
453
  * @private
@@ -378,7 +456,8 @@ class SquadDownloader {
378
456
  */
379
457
  async _downloadContents(contents, targetPath) {
380
458
  for (const item of contents) {
381
- const itemPath = path.join(targetPath, item.name);
459
+ // Validate containment BEFORE any filesystem write (zip-slip guard).
460
+ const itemPath = this._safeResolve(targetPath, item.name);
382
461
 
383
462
  if (item.type === 'file') {
384
463
  // Download file - Buffer is written directly (supports binary files)
@@ -400,9 +479,10 @@ class SquadDownloader {
400
479
  * @private
401
480
  * @param {string} url - URL to fetch
402
481
  * @param {boolean} [useApi=false] - Whether to use GitHub API headers
482
+ * @param {number} [redirectCount=0] - Current redirect depth (internal)
403
483
  * @returns {Promise<Buffer>} Response body as Buffer (supports binary files)
404
484
  */
405
- _fetch(url, useApi = false) {
485
+ _fetch(url, useApi = false, redirectCount = 0) {
406
486
  return new Promise((resolve, reject) => {
407
487
  const options = {
408
488
  headers: {
@@ -417,8 +497,7 @@ class SquadDownloader {
417
497
  }
418
498
  }
419
499
 
420
- https
421
- .get(url, options, (res) => {
500
+ const req = https.get(url, options, (res) => {
422
501
  // Check for rate limiting
423
502
  if (res.statusCode === 403) {
424
503
  const rateLimitRemaining = res.headers['x-ratelimit-remaining'];
@@ -438,7 +517,53 @@ class SquadDownloader {
438
517
 
439
518
  // Check for redirect
440
519
  if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
441
- this._fetch(res.headers.location, useApi).then(resolve).catch(reject);
520
+ // Drain the redirect response to free the socket.
521
+ res.resume();
522
+
523
+ // Cap redirect depth to prevent redirect loops.
524
+ if (redirectCount >= MAX_REDIRECTS) {
525
+ reject(
526
+ new SquadDownloaderError(
527
+ DownloaderErrorCodes.NETWORK_ERROR,
528
+ `Too many redirects (>${MAX_REDIRECTS}) while fetching ${url}`,
529
+ 'The remote server may be misconfigured or attempting a redirect loop',
530
+ ),
531
+ );
532
+ return;
533
+ }
534
+
535
+ // Resolve the Location against the current URL and enforce an
536
+ // HTTPS host allowlist (SSRF / exfiltration guard).
537
+ let redirectUrl;
538
+ try {
539
+ redirectUrl = new URL(res.headers.location, url);
540
+ } catch {
541
+ reject(
542
+ new SquadDownloaderError(
543
+ DownloaderErrorCodes.NETWORK_ERROR,
544
+ `Invalid redirect URL: ${res.headers.location}`,
545
+ ),
546
+ );
547
+ return;
548
+ }
549
+
550
+ if (
551
+ redirectUrl.protocol !== 'https:' ||
552
+ !ALLOWED_REDIRECT_HOSTS.has(redirectUrl.hostname)
553
+ ) {
554
+ reject(
555
+ new SquadDownloaderError(
556
+ DownloaderErrorCodes.VALIDATION_ERROR,
557
+ `Refusing redirect to disallowed host: ${redirectUrl.protocol}//${redirectUrl.hostname}`,
558
+ 'Redirects are restricted to trusted GitHub HTTPS hosts',
559
+ ),
560
+ );
561
+ return;
562
+ }
563
+
564
+ this._fetch(redirectUrl.toString(), useApi, redirectCount + 1)
565
+ .then(resolve)
566
+ .catch(reject);
442
567
  return;
443
568
  }
444
569
 
@@ -453,25 +578,55 @@ class SquadDownloader {
453
578
  return;
454
579
  }
455
580
 
456
- // Collect chunks as Buffer objects to support binary files
581
+ // Collect chunks as Buffer objects to support binary files, capping
582
+ // total size to prevent a malicious host from exhausting memory.
457
583
  const chunks = [];
584
+ let total = 0;
458
585
  res.on('data', (chunk) => {
586
+ total += chunk.length;
587
+ if (total > MAX_RESPONSE_BYTES) {
588
+ const err = new SquadDownloaderError(
589
+ DownloaderErrorCodes.NETWORK_ERROR,
590
+ `Response exceeded ${MAX_RESPONSE_BYTES} bytes — aborting`,
591
+ 'The remote file is unexpectedly large; verify the source',
592
+ );
593
+ if (typeof req.destroy === 'function') req.destroy(err);
594
+ else reject(err);
595
+ return;
596
+ }
459
597
  chunks.push(chunk);
460
598
  });
461
599
  res.on('end', () => {
462
600
  // Concatenate all chunks into a single Buffer
463
601
  resolve(Buffer.concat(chunks));
464
602
  });
465
- })
466
- .on('error', (error) => {
467
- reject(
468
- new SquadDownloaderError(
469
- DownloaderErrorCodes.NETWORK_ERROR,
470
- `Network error: ${error.message}`,
471
- 'Check internet connection',
472
- ),
603
+ });
604
+
605
+ // Abort hung requests (P3-001) — destroy() surfaces via the 'error' handler.
606
+ // Guarded: test doubles for https.get may not implement setTimeout/destroy.
607
+ if (typeof req.setTimeout === 'function') {
608
+ req.setTimeout(REQUEST_TIMEOUT_MS, () => {
609
+ const err = new SquadDownloaderError(
610
+ DownloaderErrorCodes.NETWORK_ERROR,
611
+ `Request timed out after ${REQUEST_TIMEOUT_MS}ms fetching ${url}`,
612
+ 'Check internet connection or try again later',
473
613
  );
614
+ if (typeof req.destroy === 'function') req.destroy(err);
615
+ else reject(err);
474
616
  });
617
+ }
618
+
619
+ req.on('error', (error) => {
620
+ reject(
621
+ error instanceof SquadDownloaderError
622
+ ? error
623
+ : new SquadDownloaderError(
624
+ DownloaderErrorCodes.NETWORK_ERROR,
625
+ `Network error: ${error.message}`,
626
+ 'Check internet connection',
627
+ ),
628
+ );
629
+ });
475
630
  });
476
631
  }
477
632
 
@@ -0,0 +1,152 @@
1
+ # delegate-to-external-executor.md
2
+
3
+ **Task**: Delegate Implementation to External Executor
4
+
5
+ **Purpose**: Standardize the orchestrator/executor split for SINAPSE workflows. The active SINAPSE runtime keeps authority over story interpretation, acceptance criteria validation, constitutional gates, review, and story updates while a separate CLI runtime performs only the implementation attempt.
6
+
7
+ **When to use**: Use only for `@developer` implementation work where the story scope is clear enough to hand to another runtime. Do not use for @product-lead, @quality-gate, @sprint-lead, @devops, architecture approval, or release authority.
8
+
9
+ ## Task Definition
10
+
11
+ ```yaml
12
+ task: delegateToExternalExecutor()
13
+ responsavel: Orchestrating agent
14
+ responsavel_type: Agente
15
+ atomic_layer: Organism
16
+
17
+ inputs:
18
+ - campo: prompt
19
+ tipo: string
20
+ obrigatorio: true
21
+ validacao: Must cite acceptance criteria, story path, file scope, and explicit non-goals
22
+ - campo: slug
23
+ tipo: string
24
+ obrigatorio: true
25
+ validacao: Stable filesystem-safe run slug
26
+ - campo: story_id
27
+ tipo: string
28
+ obrigatorio: false
29
+ - campo: story_path
30
+ tipo: string
31
+ obrigatorio: false
32
+ - campo: workdir
33
+ tipo: string
34
+ obrigatorio: false
35
+ default: Current project root
36
+ - campo: provider
37
+ tipo: string
38
+ obrigatorio: false
39
+ default: codex
40
+
41
+ outputs:
42
+ - campo: run_dir
43
+ tipo: string
44
+ destino: Orchestrator
45
+ - campo: output
46
+ tipo: file
47
+ destino: <run_dir>/output.md
48
+ - campo: log
49
+ tipo: file
50
+ destino: <run_dir>/<provider>.log
51
+ - campo: diff
52
+ tipo: git-diff
53
+ destino: Orchestrator review
54
+ ```
55
+
56
+ ## Configuration
57
+
58
+ Delegation is disabled by default.
59
+
60
+ ```yaml
61
+ dev:
62
+ execution_mode: native # native | delegate
63
+ delegate_to: codex
64
+ auto_review: true
65
+
66
+ external_executors:
67
+ enabled: false
68
+ default_sandbox: workspace-write # read-only | workspace-write | full-auto | danger-full-access
69
+ run_dir: .sinapse/external-runs
70
+ ```
71
+
72
+ ## Pre-Conditions
73
+
74
+ ```yaml
75
+ pre_conditions:
76
+ - [ ] External executor provider is installed and available on PATH.
77
+ - [ ] Working tree is clean, or existing intentional changes are already committed.
78
+ - [ ] Prompt cites the story path and acceptance criteria.
79
+ - [ ] Prompt lists allowed file scope and explicit non-goals.
80
+ - [ ] Delegated work is implementation work owned by @developer.
81
+ - [ ] Orchestrator has enough context to review the resulting diff.
82
+ ```
83
+
84
+ ## Execution
85
+
86
+ ### 1. Build the Prompt
87
+
88
+ The orchestrator writes a prompt that contains:
89
+
90
+ - Story ID and story path
91
+ - Acceptance criteria copied or summarized from the story
92
+ - Allowed file paths or modules
93
+ - Testing expectations
94
+ - Constraints from Constitution and project rules
95
+ - Explicit instruction that the executor must not update story status, checkboxes, File List, PRs, or releases
96
+
97
+ ### 2. Start the Delegate Run
98
+
99
+ Use the wrapper:
100
+
101
+ ```bash
102
+ sinapse-delegate codex -t <slug> -f <prompt_file> -d <workdir>
103
+ ```
104
+
105
+ The wrapper prints:
106
+
107
+ ```text
108
+ STATUS=started
109
+ RUN_DIR=.sinapse/external-runs/<timestamp>-<slug>
110
+ PID=<pid>
111
+ LOG=<run_dir>/codex.log
112
+ OUTPUT=<run_dir>/output.md
113
+ PROMPT=<run_dir>/prompt.md
114
+ COMMAND=<provider command>
115
+ ```
116
+
117
+ ### 3. Monitor Completion
118
+
119
+ The orchestrator may tail the log or wait for the PID. Do not mark story progress while the external executor is still running.
120
+
121
+ ### 4. Review Output and Diff
122
+
123
+ The orchestrator must read:
124
+
125
+ - `<run_dir>/output.md`
126
+ - `<run_dir>/<provider>.log`
127
+ - `git diff`
128
+
129
+ Then validate:
130
+
131
+ ```yaml
132
+ review_checklist:
133
+ - [ ] Every acceptance criterion is satisfied.
134
+ - [ ] Diff scope matches the story and prompt.
135
+ - [ ] Article IV No Invention: every change traces to a requirement.
136
+ - [ ] Tests were added or updated when behavior changed.
137
+ - [ ] Lint, typecheck, and relevant tests pass.
138
+ - [ ] No story state was mutated before review approval.
139
+ ```
140
+
141
+ ### 5. Accept or Iterate
142
+
143
+ - **Approved**: orchestrator updates story checkboxes, File List, status, and final validation evidence.
144
+ - **Rejected**: orchestrator writes specific feedback and may start a new run with a new slug or iteration suffix.
145
+
146
+ ## Anti-Patterns
147
+
148
+ - Marking a story done by trusting the executor summary without reading the diff.
149
+ - Delegating @product-lead, @quality-gate, @sprint-lead, or @devops authority to an external runtime.
150
+ - Letting the executor create PRs, push, release, or mutate story state.
151
+ - Delegating vague work without acceptance criteria and file scope.
152
+ - Running with `danger-full-access` unless the surrounding environment is externally sandboxed.
@@ -318,45 +318,64 @@ If conflicts detected, fail with message:
318
318
  Resolve conflicts before pushing.
319
319
  ```
320
320
 
321
- ### 4. Run npm run lint (if script exists)
321
+ ### 4. Run Layer 1 Quality Gate (lint + test + typecheck) — CANONICAL
322
322
 
323
- ```javascript
324
- function runNpmScript(scriptName, projectRoot) {
325
- const packageJsonPath = path.join(projectRoot, 'package.json');
326
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
323
+ > **Do NOT reimplement lint/test/typecheck here.** The framework already ships
324
+ > the canonical Layer 1 pre-commit gate (`core/quality-gates/layer1-precommit.js`),
325
+ > exposed via the CLI. Calling it keeps a single source of truth for the quality
326
+ > checks (Constitution Art. I — CLI First) instead of forking the logic.
327
327
 
328
- if (!packageJson.scripts || !packageJson.scripts[scriptName]) {
329
- console.log(`⚠️ Script "${scriptName}" not found - skipping`);
330
- return { skipped: true };
331
- }
328
+ ```bash
329
+ sinapse qa run --layer=1
330
+ ```
332
331
 
332
+ Layer 1 runs **lint (ESLint), unit tests (Jest), and typecheck** — fast local
333
+ checks — and gracefully skips any check whose npm script is absent. Exit code:
334
+ `0` = PASS, non-zero = FAIL.
335
+
336
+ ```javascript
337
+ const { execSync } = require('child_process');
338
+
339
+ function runLayer1QualityGate(projectRoot) {
333
340
  try {
334
- execSync(`npm run ${scriptName}`, {
335
- cwd: projectRoot,
336
- stdio: 'inherit'
337
- });
338
- console.log(`✓ ${scriptName} PASSED`);
341
+ // The canonical 3-check Layer 1 gate. stdio:'inherit' streams its report.
342
+ execSync('sinapse qa run --layer=1', { cwd: projectRoot, stdio: 'inherit' });
343
+ console.log('✓ Layer 1 (lint + test + typecheck) PASSED');
339
344
  return { passed: true };
340
345
  } catch (error) {
341
- console.error(`❌ ${scriptName} FAILED`);
346
+ console.error('❌ Layer 1 quality gate FAILED');
342
347
  return { passed: false, error };
343
348
  }
344
349
  }
345
350
  ```
346
351
 
347
- ### 5. Run npm test (if script exists)
352
+ ### 5. Run npm run build (if script exists)
348
353
 
349
- Same logic as lint, but for `npm test`.
354
+ Build is outside Layer 1's scope (Layer 1 is the fast lint/test/typecheck pass),
355
+ so it stays a separate step. Skips gracefully when the `build` script is absent.
350
356
 
351
- ### 6. Run npm run typecheck (if script exists)
352
-
353
- Same logic as lint, but for `npm run typecheck`.
357
+ ```javascript
358
+ function runBuild(projectRoot) {
359
+ const packageJsonPath = path.join(projectRoot, 'package.json');
360
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
354
361
 
355
- ### 7. Run npm run build (if script exists)
362
+ if (!packageJson.scripts || !packageJson.scripts.build) {
363
+ console.log('⚠️ Script "build" not found - skipping');
364
+ return { skipped: true };
365
+ }
356
366
 
357
- Same logic as lint, but for `npm run build`.
367
+ try {
368
+ execSync('npm run build', { cwd: projectRoot, stdio: 'inherit' });
369
+ console.log('✓ build PASSED');
370
+ return { passed: true };
371
+ } catch (error) {
372
+ console.error('❌ build FAILED');
373
+ return { passed: false, error };
374
+ }
375
+ }
376
+ ```
358
377
 
359
- ### 8. Run CodeRabbit CLI Review (TR-3.14.12)
378
+ ### 6. Run CodeRabbit CLI Review (TR-3.14.12)
360
379
 
361
380
  ```javascript
362
381
  const { execSync } = require('child_process');
@@ -503,7 +522,7 @@ if (coderabbitResult.gateImpact === 'CONCERNS') {
503
522
  }
504
523
  ```
505
524
 
506
- ### 9. Run Security Scan (TR-3.14.11)
525
+ ### 7. Run Security Scan (TR-3.14.11)
507
526
 
508
527
  ```javascript
509
528
  const { execSync } = require('child_process');
@@ -630,7 +649,7 @@ function determineSecurityGate(results) {
630
649
  }
631
650
  ```
632
651
 
633
- ### 9.1 Impact Analysis (Code Intelligence — Advisory Only)
652
+ ### 7.1 Impact Analysis (Code Intelligence — Advisory Only)
634
653
 
635
654
  > **Added by:** Story NOG-7 (DevOps Pre-Push Impact Analysis)
636
655
  > **Behavior:** Advisory only — NEVER blocks push. Auto-skips if code intelligence unavailable.
@@ -688,7 +707,7 @@ Impact Analysis:
688
707
 
689
708
  ---
690
709
 
691
- ### 10. Verify Story Status (Optional - if using story-driven workflow)
710
+ ### 8. Verify Story Status (Optional - if using story-driven workflow)
692
711
 
693
712
  ```javascript
694
713
  function checkStoryStatus(storyPath) {
@@ -735,9 +754,7 @@ Mode: {framework-development | project-development}
735
754
  Quality Checks:
736
755
  ✓ No uncommitted changes
737
756
  ✓ No merge conflicts
738
- npm run lint PASSED
739
- ✓ npm test PASSED
740
- ✓ npm run typecheck PASSED
757
+ Layer 1 (lint+test+typecheck) PASSED (via `sinapse qa run --layer=1`)
741
758
  ✓ npm run build PASSED
742
759
  ✓ Security scan PASSED
743
760
  ⚠️ Story status SKIPPED (no story file)
@@ -1,11 +1,11 @@
1
1
  # Task: Update SINAPSE Framework
2
2
 
3
- > **Version:** 4.0.0
3
+ > **Version:** 5.2.0
4
4
  > **Created:** 2026-01-29
5
- > **Updated:** 2026-01-31
5
+ > **Updated:** 2026-06-15
6
6
  > **Type:** SYNC (git-native framework synchronization)
7
7
  > **Agent:** @devops (Pipeline) or @sinapse (Orion)
8
- > **Execution:** Simple bash script (~15 lines)
8
+ > **Execution:** Bash script (~150 lines)
9
9
 
10
10
  ## Purpose
11
11
 
@@ -39,15 +39,12 @@ function readStdin() {
39
39
  });
40
40
  }
41
41
 
42
+ // Story 10.47: delegate to the shared grounding config loader instead of
43
+ // duplicating the read+parse. The require is guarded so the hook stays
44
+ // fail-open even if the shared module is somehow absent at runtime.
42
45
  function loadConfig() {
43
46
  try {
44
- if (!fs.existsSync(CONFIG_PATH)) return null;
45
- const raw = fs.readFileSync(CONFIG_PATH, 'utf8');
46
- let yaml;
47
- try { yaml = require('js-yaml'); } catch { return null; }
48
- const parsed = yaml.load(raw);
49
- if (!parsed || typeof parsed !== 'object') return null;
50
- return parsed;
47
+ return require('../core/grounding/config-loader.cjs').loadGroundingConfig(CONFIG_PATH);
51
48
  } catch {
52
49
  return null;
53
50
  }
@@ -11,7 +11,7 @@
11
11
  * (PT + EN). Injects the first 3000 chars of the DS law file as
12
12
  * `<ds-grounding>` context.
13
13
  *
14
- * Coexistence: Caio's personal `~/.claude/hooks/design-system-grounding.cjs`
14
+ * Coexistence: the user's personal `~/.claude/hooks/design-system-grounding.cjs` (if present)
15
15
  * reads `ds-routing.json` and is unaffected by this file.
16
16
  */
17
17
 
@@ -66,15 +66,12 @@ function readStdin() {
66
66
  });
67
67
  }
68
68
 
69
+ // Story 10.47: delegate to the shared grounding config loader instead of
70
+ // duplicating the read+parse. The require is guarded so the hook stays
71
+ // fail-open even if the shared module is somehow absent at runtime.
69
72
  function loadConfig() {
70
73
  try {
71
- if (!fs.existsSync(CONFIG_PATH)) return null;
72
- const raw = fs.readFileSync(CONFIG_PATH, 'utf8');
73
- let yaml;
74
- try { yaml = require('js-yaml'); } catch { return null; }
75
- const parsed = yaml.load(raw);
76
- if (!parsed || typeof parsed !== 'object') return null;
77
- return parsed;
74
+ return require('../core/grounding/config-loader.cjs').loadGroundingConfig(CONFIG_PATH);
78
75
  } catch {
79
76
  return null;
80
77
  }
@@ -13,8 +13,8 @@
13
13
  *
14
14
  * Coexistence with personal hooks: this file lives at
15
15
  * .sinapse-ai/hooks/sinapse-vault-grounding.cjs
16
- * and reads `sinapse-ai-config.yaml`. Caio's personal
17
- * `~/.claude/hooks/vault-grounding.cjs` reads `vault-routing.json`. The two
16
+ * and reads `sinapse-ai-config.yaml`. The user's personal
17
+ * `~/.claude/hooks/vault-grounding.cjs` (if present) reads `vault-routing.json`. The two
18
18
  * files do not collide (different filenames, different config sources, and
19
19
  * fail-open guarantees that running both is safe).
20
20
  *
@@ -54,15 +54,12 @@ function readStdin() {
54
54
  });
55
55
  }
56
56
 
57
+ // Story 10.47: delegate to the shared grounding config loader instead of
58
+ // duplicating the read+parse. The require is guarded so the hook stays
59
+ // fail-open even if the shared module is somehow absent at runtime.
57
60
  function loadConfig() {
58
61
  try {
59
- if (!fs.existsSync(CONFIG_PATH)) return null;
60
- const raw = fs.readFileSync(CONFIG_PATH, 'utf8');
61
- let yaml;
62
- try { yaml = require('js-yaml'); } catch { return null; }
63
- const parsed = yaml.load(raw);
64
- if (!parsed || typeof parsed !== 'object') return null;
65
- return parsed;
62
+ return require('../core/grounding/config-loader.cjs').loadGroundingConfig(CONFIG_PATH);
66
63
  } catch {
67
64
  return null;
68
65
  }
@@ -42,7 +42,10 @@ const DEFAULT_CONFIG = {
42
42
  },
43
43
  },
44
44
  claude: {
45
- model: 'claude-3-5-sonnet',
45
+ // No hardcoded model: stale IDs get rejected by the CLI ("model may not
46
+ // exist"). null → omit --model and use the user's CLI default, which is
47
+ // always a valid, current model.
48
+ model: null,
46
49
  timeout: 300000,
47
50
  dangerouslySkipPermissions: false,
48
51
  },