brainclaw 0.29.2 → 1.5.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 (195) hide show
  1. package/README.md +193 -170
  2. package/dist/brainclaw-vscode.vsix +0 -0
  3. package/dist/cli.js +673 -24
  4. package/dist/commands/accept.js +3 -0
  5. package/dist/commands/add-step.js +11 -26
  6. package/dist/commands/agent-board.js +70 -3
  7. package/dist/commands/audit.js +19 -0
  8. package/dist/commands/check-policy.js +54 -0
  9. package/dist/commands/check-security-mcp.js +145 -0
  10. package/dist/commands/check-security.js +106 -0
  11. package/dist/commands/claim-resource.js +1 -0
  12. package/dist/commands/codev.js +672 -0
  13. package/dist/commands/compact.js +74 -0
  14. package/dist/commands/complete-step.js +16 -26
  15. package/dist/commands/constraint.js +8 -20
  16. package/dist/commands/decision.js +9 -20
  17. package/dist/commands/delete-plan.js +10 -12
  18. package/dist/commands/delete-step.js +16 -0
  19. package/dist/commands/dispatch.js +163 -0
  20. package/dist/commands/doctor.js +1122 -49
  21. package/dist/commands/enable-agent.js +1 -0
  22. package/dist/commands/export.js +280 -22
  23. package/dist/commands/handoff.js +33 -0
  24. package/dist/commands/harvest.js +189 -0
  25. package/dist/commands/hooks.js +82 -25
  26. package/dist/commands/inbox.js +169 -0
  27. package/dist/commands/init.js +38 -31
  28. package/dist/commands/install-hooks.js +71 -44
  29. package/dist/commands/link.js +89 -0
  30. package/dist/commands/list-claims.js +48 -3
  31. package/dist/commands/list-plans.js +129 -25
  32. package/dist/commands/loops-handlers.js +409 -0
  33. package/dist/commands/mcp-read-handlers.js +1628 -0
  34. package/dist/commands/mcp-schemas.generated.js +74 -0
  35. package/dist/commands/mcp.js +4221 -1501
  36. package/dist/commands/plan-resource.js +64 -0
  37. package/dist/commands/plan.js +12 -26
  38. package/dist/commands/prune.js +37 -2
  39. package/dist/commands/reflect.js +20 -7
  40. package/dist/commands/release-claim.js +11 -6
  41. package/dist/commands/release-notes.js +170 -0
  42. package/dist/commands/repair.js +210 -0
  43. package/dist/commands/run-profile.js +57 -0
  44. package/dist/commands/sequence.js +113 -0
  45. package/dist/commands/session-end.js +423 -14
  46. package/dist/commands/session-start.js +214 -41
  47. package/dist/commands/setup-security.js +103 -0
  48. package/dist/commands/setup.js +42 -4
  49. package/dist/commands/stale.js +109 -0
  50. package/dist/commands/switch.js +100 -2
  51. package/dist/commands/trap.js +14 -31
  52. package/dist/commands/update-handoff.js +63 -4
  53. package/dist/commands/update-plan.js +21 -28
  54. package/dist/commands/update-step.js +37 -0
  55. package/dist/commands/upgrade.js +313 -6
  56. package/dist/commands/usage.js +102 -0
  57. package/dist/commands/version.js +20 -0
  58. package/dist/commands/who.js +33 -5
  59. package/dist/commands/worktree.js +105 -0
  60. package/dist/core/actions.js +315 -0
  61. package/dist/core/agent-capability.js +610 -17
  62. package/dist/core/agent-context.js +7 -1
  63. package/dist/core/agent-files.js +1169 -85
  64. package/dist/core/agent-integrations.js +160 -5
  65. package/dist/core/agent-inventory.js +2 -0
  66. package/dist/core/agent-profiles.js +93 -0
  67. package/dist/core/agent-registry.js +162 -30
  68. package/dist/core/agentrun-reconciler.js +345 -0
  69. package/dist/core/agentruns.js +424 -0
  70. package/dist/core/ai-agent-detection.js +31 -10
  71. package/dist/core/archival.js +77 -0
  72. package/dist/core/assignment-sweeper.js +82 -0
  73. package/dist/core/assignments.js +367 -0
  74. package/dist/core/audit.js +30 -0
  75. package/dist/core/brainclaw-version.js +94 -2
  76. package/dist/core/candidates.js +93 -2
  77. package/dist/core/claims.js +419 -0
  78. package/dist/core/codev-metrics.js +77 -0
  79. package/dist/core/codev-personas.js +31 -0
  80. package/dist/core/codev-plan-gen.js +35 -0
  81. package/dist/core/codev-prompts.js +74 -0
  82. package/dist/core/codev-responses.js +62 -0
  83. package/dist/core/codev-rounds.js +218 -0
  84. package/dist/core/config.js +4 -0
  85. package/dist/core/context.js +381 -34
  86. package/dist/core/coordination.js +201 -6
  87. package/dist/core/cross-project.js +230 -16
  88. package/dist/core/default-profiles/doctor.yaml +11 -0
  89. package/dist/core/default-profiles/janitor.yaml +11 -0
  90. package/dist/core/default-profiles/onboarder.yaml +11 -0
  91. package/dist/core/default-profiles/reviewer.yaml +13 -0
  92. package/dist/core/dispatcher.js +1189 -0
  93. package/dist/core/duplicates.js +2 -2
  94. package/dist/core/entity-operations.js +450 -0
  95. package/dist/core/entity-registry.js +344 -0
  96. package/dist/core/events.js +106 -2
  97. package/dist/core/execution-adapters.js +154 -0
  98. package/dist/core/execution-context.js +63 -0
  99. package/dist/core/execution-profile.js +270 -0
  100. package/dist/core/execution.js +255 -0
  101. package/dist/core/facade-schema.js +81 -0
  102. package/dist/core/federation-cloud.js +99 -0
  103. package/dist/core/federation-message.js +52 -0
  104. package/dist/core/federation-transport.js +65 -0
  105. package/dist/core/gc-semantic.js +482 -0
  106. package/dist/core/governance.js +247 -0
  107. package/dist/core/guards.js +19 -0
  108. package/dist/core/ideation.js +72 -0
  109. package/dist/core/identity.js +110 -25
  110. package/dist/core/ids.js +6 -0
  111. package/dist/core/input-validation.js +2 -2
  112. package/dist/core/instruction-templates.js +344 -136
  113. package/dist/core/io.js +90 -11
  114. package/dist/core/lock.js +6 -2
  115. package/dist/core/loops/brief-assembly.js +213 -0
  116. package/dist/core/loops/facade-schema.js +148 -0
  117. package/dist/core/loops/index.js +7 -0
  118. package/dist/core/loops/iteration-engine.js +139 -0
  119. package/dist/core/loops/lock.js +385 -0
  120. package/dist/core/loops/store.js +201 -0
  121. package/dist/core/loops/types.js +403 -0
  122. package/dist/core/loops/verbs.js +534 -0
  123. package/dist/core/markdown.js +15 -3
  124. package/dist/core/memory-compactor.js +432 -0
  125. package/dist/core/memory-git.js +152 -8
  126. package/dist/core/messaging.js +278 -0
  127. package/dist/core/migration.js +32 -1
  128. package/dist/core/mutation-pipeline.js +4 -2
  129. package/dist/core/operations/memory-mutation.js +129 -0
  130. package/dist/core/operations/memory-write.js +78 -0
  131. package/dist/core/operations/plan.js +190 -0
  132. package/dist/core/policy.js +169 -0
  133. package/dist/core/reputation.js +9 -3
  134. package/dist/core/schema.js +491 -6
  135. package/dist/core/search.js +21 -2
  136. package/dist/core/security-cache.js +71 -0
  137. package/dist/core/security-guard.js +152 -0
  138. package/dist/core/security-scoring.js +86 -0
  139. package/dist/core/sequence.js +130 -0
  140. package/dist/core/socket-client.js +113 -0
  141. package/dist/core/staleness.js +246 -0
  142. package/dist/core/state.js +98 -22
  143. package/dist/core/store-resolution.js +43 -11
  144. package/dist/core/toml-writer.js +76 -0
  145. package/dist/core/upgrades/backup.js +232 -0
  146. package/dist/core/upgrades/health-check.js +169 -0
  147. package/dist/core/upgrades/patches/candidate-archive.js +145 -0
  148. package/dist/core/upgrades/patches/handoff-review-strip.js +128 -0
  149. package/dist/core/upgrades/patches/provenance-rollout.js +136 -0
  150. package/dist/core/upgrades/schema-version.js +97 -0
  151. package/dist/core/worktree.js +606 -0
  152. package/dist/facts.js +114 -0
  153. package/dist/facts.json +111 -0
  154. package/docs/architecture/project-refs.md +5 -1
  155. package/docs/cli.md +690 -43
  156. package/docs/concepts/ideation-loop.md +317 -0
  157. package/docs/concepts/loop-engine.md +456 -0
  158. package/docs/concepts/mcp-governance.md +268 -0
  159. package/docs/concepts/memory-staleness.md +122 -0
  160. package/docs/concepts/multi-agent-workflows.md +166 -0
  161. package/docs/concepts/plans-and-claims.md +31 -6
  162. package/docs/concepts/project-md-convention.md +35 -0
  163. package/docs/concepts/troubleshooting.md +220 -0
  164. package/docs/concepts/upgrade-cli.md +202 -0
  165. package/docs/concepts/upgrade-dogfood-procedure.md +114 -0
  166. package/docs/context-format-changelog.md +2 -2
  167. package/docs/context-format.md +2 -2
  168. package/docs/index.md +68 -0
  169. package/docs/integrations/agents.md +15 -16
  170. package/docs/integrations/cline.md +88 -0
  171. package/docs/integrations/codex.md +75 -23
  172. package/docs/integrations/continue.md +60 -0
  173. package/docs/integrations/copilot.md +67 -9
  174. package/docs/integrations/kilocode.md +72 -0
  175. package/docs/integrations/mcp.md +304 -21
  176. package/docs/integrations/mistral-vibe.md +122 -0
  177. package/docs/integrations/opencode.md +84 -0
  178. package/docs/integrations/overview.md +23 -8
  179. package/docs/integrations/roo.md +74 -0
  180. package/docs/integrations/windsurf.md +83 -0
  181. package/docs/mcp-schema-changelog.md +191 -1
  182. package/docs/playbooks/integration/index.md +121 -0
  183. package/docs/playbooks/productivity/index.md +102 -0
  184. package/docs/playbooks/team/index.md +122 -0
  185. package/docs/product/agent-first-model.md +184 -0
  186. package/docs/product/entity-model-audit.md +462 -0
  187. package/docs/quickstart-existing-project.md +135 -0
  188. package/docs/quickstart.md +124 -37
  189. package/docs/release-maintenance.md +79 -0
  190. package/docs/review.md +2 -0
  191. package/docs/server-operations.md +118 -0
  192. package/package.json +20 -12
  193. package/dist/commands/claude-desktop-extension.js +0 -18
  194. package/dist/commands/diff.js +0 -99
  195. package/dist/core/claude-desktop-extension.js +0 -224
@@ -1,5 +1,6 @@
1
1
  import { loadState } from './state.js';
2
2
  import { listCandidates } from './candidates.js';
3
+ import { listSequences } from './sequence.js';
3
4
  const K1 = 1.5;
4
5
  const B = 0.75;
5
6
  function tokenize(text) {
@@ -9,7 +10,7 @@ function tokenize(text) {
9
10
  .split(/\s+/)
10
11
  .filter(t => t.length > 1);
11
12
  }
12
- function buildCorpus(state, includePending) {
13
+ function buildCorpus(state, includePending, cwd) {
13
14
  const docs = [];
14
15
  const add = (section, item) => {
15
16
  const textParts = [item.text, item.author ?? '', ...(item.tags ?? []), ...(item.related_paths ?? [])];
@@ -25,6 +26,24 @@ function buildCorpus(state, includePending) {
25
26
  add('handoffs', { ...h, text: `${h.from} -> ${h.to}: ${h.text}` });
26
27
  for (const p of state.plan_items)
27
28
  add('plans', p);
29
+ // Index sequences: combine name, description, item labels/lanes into searchable text
30
+ try {
31
+ const sequences = listSequences(cwd);
32
+ for (const seq of sequences) {
33
+ const itemTexts = (seq.items ?? []).map((it) => [it.lane, it.scope_hint, it.label, it.rationale].filter(Boolean).join(' '));
34
+ const text = [seq.name, seq.description ?? '', ...itemTexts].join(' — ');
35
+ add('sequences', {
36
+ id: seq.id,
37
+ text,
38
+ author: seq.author,
39
+ created_at: seq.created_at,
40
+ tags: seq.tags ?? [],
41
+ });
42
+ }
43
+ }
44
+ catch {
45
+ // Sequences dir may not exist yet — non-fatal
46
+ }
28
47
  if (includePending) {
29
48
  const candidates = listCandidates('pending');
30
49
  for (const c of candidates)
@@ -109,7 +128,7 @@ export function searchCorpus(documents, options) {
109
128
  }
110
129
  export function search(options) {
111
130
  const state = loadState(options.cwd);
112
- const corpus = buildCorpus(state, options.includePending ?? false);
131
+ const corpus = buildCorpus(state, options.includePending ?? false, options.cwd);
113
132
  return searchCorpus(corpus, options);
114
133
  }
115
134
  //# sourceMappingURL=search.js.map
@@ -0,0 +1,71 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ function cacheKey(ecosystem, depname, version) {
4
+ return `${ecosystem}/${depname}@${version}`;
5
+ }
6
+ function loadStore(cachePath) {
7
+ try {
8
+ const raw = fs.readFileSync(cachePath, 'utf-8');
9
+ const parsed = JSON.parse(raw);
10
+ if (parsed?.version === 1 && typeof parsed.entries === 'object') {
11
+ return parsed;
12
+ }
13
+ }
14
+ catch {
15
+ // file missing or corrupt — start fresh
16
+ }
17
+ return { version: 1, entries: {} };
18
+ }
19
+ function saveStore(cachePath, store) {
20
+ const dir = path.dirname(cachePath);
21
+ if (!fs.existsSync(dir))
22
+ fs.mkdirSync(dir, { recursive: true });
23
+ fs.writeFileSync(cachePath, JSON.stringify(store, null, 2), 'utf-8');
24
+ }
25
+ export class SecurityCache {
26
+ store;
27
+ cachePath;
28
+ ttlMs;
29
+ constructor(cachePath, ttlHours = 24) {
30
+ this.cachePath = cachePath;
31
+ this.ttlMs = ttlHours * 60 * 60 * 1000;
32
+ this.store = loadStore(cachePath);
33
+ }
34
+ get(ecosystem, depname, version) {
35
+ const key = cacheKey(ecosystem, depname, version);
36
+ const entry = this.store.entries[key];
37
+ if (!entry)
38
+ return null;
39
+ const age = Date.now() - new Date(entry.fetched_at).getTime();
40
+ if (age > this.ttlMs) {
41
+ delete this.store.entries[key];
42
+ return null;
43
+ }
44
+ return entry.scores;
45
+ }
46
+ set(ecosystem, depname, version, scores) {
47
+ const key = cacheKey(ecosystem, depname, version);
48
+ this.store.entries[key] = {
49
+ scores,
50
+ fetched_at: new Date().toISOString(),
51
+ };
52
+ }
53
+ flush() {
54
+ saveStore(this.cachePath, this.store);
55
+ }
56
+ prune() {
57
+ const now = Date.now();
58
+ let pruned = 0;
59
+ for (const [key, entry] of Object.entries(this.store.entries)) {
60
+ if (now - new Date(entry.fetched_at).getTime() > this.ttlMs) {
61
+ delete this.store.entries[key];
62
+ pruned++;
63
+ }
64
+ }
65
+ return pruned;
66
+ }
67
+ size() {
68
+ return Object.keys(this.store.entries).length;
69
+ }
70
+ }
71
+ //# sourceMappingURL=security-cache.js.map
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Generates brainclaw-guard wrapper scripts (bash + PowerShell) that intercept
3
+ * npm/pip install commands and check packages via brainclaw check-security.
4
+ */
5
+ export function generateBashGuard(brainclawBin) {
6
+ return `#!/usr/bin/env bash
7
+ # brainclaw-guard — preinstall security gate
8
+ # Generated by: brainclaw setup --security
9
+ # Do not edit manually — regenerate with brainclaw setup --security
10
+
11
+ BRAINCLAW_BIN="${brainclawBin}"
12
+ ORIGINAL_CMD="\${BRAINCLAW_GUARD_ORIGINAL_CMD:-npm}"
13
+
14
+ is_install_command() {
15
+ for arg in "$@"; do
16
+ case "$arg" in
17
+ install|add|i) return 0 ;;
18
+ -*) continue ;;
19
+ *) break ;;
20
+ esac
21
+ done
22
+ return 1
23
+ }
24
+
25
+ extract_packages() {
26
+ local packages=""
27
+ local skip_next=false
28
+ local past_command=false
29
+ for arg in "$@"; do
30
+ if [ "$skip_next" = true ]; then
31
+ skip_next=false
32
+ continue
33
+ fi
34
+ case "$arg" in
35
+ install|add|i)
36
+ past_command=true
37
+ continue
38
+ ;;
39
+ --save-dev|--save-peer|--save-optional|-D|-P|-O|-g|--global)
40
+ continue
41
+ ;;
42
+ --registry|--prefix)
43
+ skip_next=true
44
+ continue
45
+ ;;
46
+ -*)
47
+ continue
48
+ ;;
49
+ *)
50
+ if [ "$past_command" = true ]; then
51
+ if [ -n "$packages" ]; then
52
+ packages="$packages,$arg"
53
+ else
54
+ packages="$arg"
55
+ fi
56
+ fi
57
+ ;;
58
+ esac
59
+ done
60
+ echo "$packages"
61
+ }
62
+
63
+ # Main logic
64
+ if is_install_command "$@"; then
65
+ packages=$(extract_packages "$@")
66
+ if [ -n "$packages" ]; then
67
+ ecosystem="npm"
68
+ if [ "$ORIGINAL_CMD" = "pip" ] || [ "$ORIGINAL_CMD" = "pip3" ]; then
69
+ ecosystem="pypi"
70
+ fi
71
+
72
+ result=$("$BRAINCLAW_BIN" check-security --packages "$packages" --ecosystem "$ecosystem" --json 2>/dev/null)
73
+ exit_code=$?
74
+
75
+ if [ $exit_code -eq 2 ]; then
76
+ echo "[brainclaw-guard] BLOCKED — supply chain risk detected" >&2
77
+ echo "[brainclaw-guard] Run: brainclaw check-security --packages \\"$packages\\" for details" >&2
78
+ exit 1
79
+ elif [ $exit_code -eq 1 ]; then
80
+ echo "[brainclaw-guard] WARNING — potential supply chain risk" >&2
81
+ echo "[brainclaw-guard] Run: brainclaw check-security --packages \\"$packages\\" for details" >&2
82
+ fi
83
+ # exit_code 0 = pass, continue silently
84
+ fi
85
+ fi
86
+
87
+ exec "$ORIGINAL_CMD" "$@"
88
+ `;
89
+ }
90
+ export function generatePowerShellGuard(brainclawBin) {
91
+ return `# brainclaw-guard — preinstall security gate (PowerShell)
92
+ # Generated by: brainclaw setup --security
93
+ # Do not edit manually — regenerate with brainclaw setup --security
94
+
95
+ param([Parameter(ValueFromRemainingArguments)]$Args)
96
+
97
+ $BrainclawBin = "${brainclawBin}"
98
+ $OriginalCmd = if ($env:BRAINCLAW_GUARD_ORIGINAL_CMD) { $env:BRAINCLAW_GUARD_ORIGINAL_CMD } else { "npm" }
99
+
100
+ function Is-InstallCommand($arguments) {
101
+ foreach ($arg in $arguments) {
102
+ if ($arg -match "^(install|add|i)$") { return $true }
103
+ if ($arg -notmatch "^-") { break }
104
+ }
105
+ return $false
106
+ }
107
+
108
+ function Extract-Packages($arguments) {
109
+ $packages = @()
110
+ $pastCommand = $false
111
+ $skipNext = $false
112
+ foreach ($arg in $arguments) {
113
+ if ($skipNext) { $skipNext = $false; continue }
114
+ if ($arg -match "^(install|add|i)$") { $pastCommand = $true; continue }
115
+ if ($arg -match "^--(registry|prefix)$") { $skipNext = $true; continue }
116
+ if ($arg -match "^-") { continue }
117
+ if ($pastCommand) { $packages += $arg }
118
+ }
119
+ return ($packages -join ",")
120
+ }
121
+
122
+ if (Is-InstallCommand $Args) {
123
+ $packages = Extract-Packages $Args
124
+ if ($packages) {
125
+ $ecosystem = "npm"
126
+ if ($OriginalCmd -match "^pip") { $ecosystem = "pypi" }
127
+
128
+ try {
129
+ & $BrainclawBin check-security --packages $packages --ecosystem $ecosystem --json 2>$null | Out-Null
130
+ $exitCode = $LASTEXITCODE
131
+ } catch {
132
+ $exitCode = 0
133
+ }
134
+
135
+ if ($exitCode -eq 2) {
136
+ Write-Error "[brainclaw-guard] BLOCKED - supply chain risk detected"
137
+ Write-Error "[brainclaw-guard] Run: brainclaw check-security --packages \`"$packages\`" for details"
138
+ exit 1
139
+ } elseif ($exitCode -eq 1) {
140
+ Write-Warning "[brainclaw-guard] WARNING - potential supply chain risk"
141
+ Write-Warning "[brainclaw-guard] Run: brainclaw check-security --packages \`"$packages\`" for details"
142
+ }
143
+ }
144
+ }
145
+
146
+ & $OriginalCmd @Args
147
+ `;
148
+ }
149
+ export function generatePipBashGuard(brainclawBin) {
150
+ return generateBashGuard(brainclawBin).replace('ORIGINAL_CMD="${BRAINCLAW_GUARD_ORIGINAL_CMD:-npm}"', 'ORIGINAL_CMD="${BRAINCLAW_GUARD_ORIGINAL_CMD:-pip}"');
151
+ }
152
+ //# sourceMappingURL=security-guard.js.map
@@ -0,0 +1,86 @@
1
+ const DEFAULT_WEIGHTS = {
2
+ supply_chain: 0.35,
3
+ vulnerability: 0.30,
4
+ quality: 0.15,
5
+ maintenance: 0.15,
6
+ license: 0.05,
7
+ };
8
+ const DEFAULT_THRESHOLDS = {
9
+ composite_pass: 70,
10
+ composite_warn: 50,
11
+ supply_chain_block: 30,
12
+ vulnerability_block: 20,
13
+ };
14
+ export function computeComposite(scores, weights) {
15
+ const w = { ...DEFAULT_WEIGHTS, ...weights };
16
+ return Math.round((scores.supplyChain * w.supply_chain +
17
+ scores.vulnerability * w.vulnerability +
18
+ scores.quality * w.quality +
19
+ scores.maintenance * w.maintenance +
20
+ scores.license * w.license) * 10) / 10;
21
+ }
22
+ export function evaluatePackage(scores, config) {
23
+ const thresholds = { ...DEFAULT_THRESHOLDS, ...config?.thresholds };
24
+ const weights = { ...DEFAULT_WEIGHTS, ...config?.weights };
25
+ const allowlist = config?.allowlist ?? [];
26
+ const denylist = config?.denylist ?? [];
27
+ const pkgName = scores.purl.replace(/^pkg:\w+\//, '');
28
+ const ecosystem = scores.purl.startsWith('pkg:pypi') ? 'pypi' : 'npm';
29
+ const reasons = [];
30
+ // Denylist check (exact match on package name)
31
+ if (denylist.some(d => pkgName === d || scores.purl.includes(d))) {
32
+ return {
33
+ package: pkgName,
34
+ ecosystem,
35
+ version: scores.version,
36
+ scores,
37
+ composite: 0,
38
+ decision: 'block',
39
+ reasons: [`Package "${pkgName}" is on the denylist`],
40
+ };
41
+ }
42
+ // Allowlist check (skip scoring)
43
+ if (allowlist.some(a => pkgName === a || scores.purl.includes(a))) {
44
+ return {
45
+ package: pkgName,
46
+ ecosystem,
47
+ version: scores.version,
48
+ scores,
49
+ composite: 100,
50
+ decision: 'pass',
51
+ reasons: [`Package "${pkgName}" is on the allowlist`],
52
+ };
53
+ }
54
+ const composite = computeComposite(scores, weights);
55
+ // Hard blocks on individual scores
56
+ if (scores.supplyChain < thresholds.supply_chain_block) {
57
+ reasons.push(`supply_chain=${scores.supplyChain} < ${thresholds.supply_chain_block} (hard block)`);
58
+ }
59
+ if (scores.vulnerability < thresholds.vulnerability_block) {
60
+ reasons.push(`vulnerability=${scores.vulnerability} < ${thresholds.vulnerability_block} (hard block)`);
61
+ }
62
+ if (reasons.length > 0) {
63
+ return { package: pkgName, ecosystem, version: scores.version, scores, composite, decision: 'block', reasons };
64
+ }
65
+ // Composite-based decision
66
+ if (composite < thresholds.composite_warn) {
67
+ reasons.push(`composite=${composite} < ${thresholds.composite_warn} (block threshold)`);
68
+ return { package: pkgName, ecosystem, version: scores.version, scores, composite, decision: 'block', reasons };
69
+ }
70
+ if (composite < thresholds.composite_pass) {
71
+ reasons.push(`composite=${composite} < ${thresholds.composite_pass} (warn threshold)`);
72
+ return { package: pkgName, ecosystem, version: scores.version, scores, composite, decision: 'warn', reasons };
73
+ }
74
+ return { package: pkgName, ecosystem, version: scores.version, scores, composite, decision: 'pass', reasons: [] };
75
+ }
76
+ export function evaluateBatch(scoresList, config) {
77
+ return scoresList.map(s => evaluatePackage(s, config));
78
+ }
79
+ export function worstDecision(verdicts) {
80
+ if (verdicts.some(v => v.decision === 'block'))
81
+ return 'block';
82
+ if (verdicts.some(v => v.decision === 'warn'))
83
+ return 'warn';
84
+ return 'pass';
85
+ }
86
+ //# sourceMappingURL=security-scoring.js.map
@@ -0,0 +1,130 @@
1
+ import fs from 'node:fs';
2
+ import { JsonStore } from './json-store.js';
3
+ import { mutate } from './mutation-pipeline.js';
4
+ import { generateIdWithLabel, nowISO } from './ids.js';
5
+ import { resolveEntityDir } from './io.js';
6
+ import { SequenceItemSchema, SequenceSchema } from './schema.js';
7
+ import { refreshLiveCompanions } from '../commands/export.js';
8
+ function sequencesDir(cwd, mode = 'read') {
9
+ return resolveEntityDir('sequences', cwd ?? process.cwd(), mode);
10
+ }
11
+ export function ensureSequencesDir(cwd) {
12
+ const dir = sequencesDir(cwd, 'write');
13
+ if (!fs.existsSync(dir)) {
14
+ fs.mkdirSync(dir, { recursive: true });
15
+ }
16
+ }
17
+ function sequenceStore(cwd, mode = 'read') {
18
+ return new JsonStore({
19
+ dirPath: sequencesDir(cwd, mode),
20
+ documentType: 'sequence',
21
+ getId: (sequence) => sequence.id,
22
+ sort: (a, b) => a.updated_at.localeCompare(b.updated_at),
23
+ });
24
+ }
25
+ function normalizeItems(items) {
26
+ return items
27
+ .map((item) => SequenceItemSchema.parse(item))
28
+ .sort((a, b) => a.rank - b.rank || a.planId.localeCompare(b.planId));
29
+ }
30
+ function validateRanks(items) {
31
+ const ranks = new Set();
32
+ for (const item of items) {
33
+ if (ranks.has(item.rank)) {
34
+ throw new Error(`Duplicate sequence rank: ${item.rank}`);
35
+ }
36
+ ranks.add(item.rank);
37
+ }
38
+ }
39
+ export function saveSequence(sequence, cwd) {
40
+ mutate({ cwd }, () => {
41
+ ensureSequencesDir(cwd);
42
+ sequenceStore(cwd, 'write').save(SequenceSchema.parse(sequence));
43
+ // Auto-refresh live companions after sequence changes (non-fatal)
44
+ try {
45
+ refreshLiveCompanions(cwd);
46
+ }
47
+ catch { /* best-effort */ }
48
+ });
49
+ }
50
+ export function listSequences(cwd) {
51
+ return sequenceStore(cwd).list().sort((a, b) => {
52
+ const activeBoost = Number(b.status === 'active') - Number(a.status === 'active');
53
+ if (activeBoost !== 0)
54
+ return activeBoost;
55
+ return b.updated_at.localeCompare(a.updated_at);
56
+ });
57
+ }
58
+ export function loadSequence(id, cwd) {
59
+ const sequence = listSequences(cwd).find((entry) => entry.id === id || entry.short_label === id);
60
+ if (!sequence) {
61
+ throw new Error(`Sequence not found: ${id}`);
62
+ }
63
+ return sequence;
64
+ }
65
+ export function getActiveSequence(cwd) {
66
+ return listSequences(cwd).find((sequence) => sequence.status === 'active');
67
+ }
68
+ export function createSequence(input, cwd) {
69
+ const items = normalizeItems(input.items ?? []);
70
+ validateRanks(items);
71
+ const { id, short_label } = generateIdWithLabel('sequences', cwd);
72
+ const timestamp = nowISO();
73
+ const sequence = {
74
+ id,
75
+ short_label,
76
+ name: input.name,
77
+ description: input.description,
78
+ status: input.status ?? 'draft',
79
+ items,
80
+ owner: input.owner,
81
+ created_at: timestamp,
82
+ updated_at: timestamp,
83
+ author: input.author,
84
+ author_id: input.authorId,
85
+ model: input.model,
86
+ project_id: input.projectId,
87
+ host_id: input.hostId,
88
+ session_id: input.sessionId,
89
+ tags: input.tags ?? [],
90
+ };
91
+ saveSequence(sequence, cwd);
92
+ return { id, shortLabel: short_label, name: input.name };
93
+ }
94
+ export function deleteSequence(id, cwd) {
95
+ return mutate({ cwd }, () => {
96
+ ensureSequencesDir(cwd);
97
+ const store = sequenceStore(cwd, 'write');
98
+ const current = store.list().find((entry) => entry.id === id || entry.short_label === id);
99
+ if (!current) {
100
+ throw new Error(`Sequence not found: ${id}`);
101
+ }
102
+ store.delete(current.id);
103
+ return { id: current.id, name: current.name };
104
+ });
105
+ }
106
+ export function updateSequence(input, cwd) {
107
+ return mutate({ cwd }, () => {
108
+ ensureSequencesDir(cwd);
109
+ const store = sequenceStore(cwd, 'write');
110
+ const current = store.list().find((entry) => entry.id === input.id || entry.short_label === input.id);
111
+ if (!current) {
112
+ throw new Error(`Sequence not found: ${input.id}`);
113
+ }
114
+ const items = input.items ? normalizeItems(input.items) : current.items;
115
+ validateRanks(items);
116
+ const next = {
117
+ ...current,
118
+ name: input.name ?? current.name,
119
+ description: input.description ?? current.description,
120
+ status: input.status ?? current.status,
121
+ items,
122
+ owner: input.owner ?? current.owner,
123
+ tags: input.tags ?? current.tags,
124
+ updated_at: nowISO(),
125
+ };
126
+ store.save(SequenceSchema.parse(next));
127
+ return next;
128
+ });
129
+ }
130
+ //# sourceMappingURL=sequence.js.map
@@ -0,0 +1,113 @@
1
+ import https from 'node:https';
2
+ const DEFAULT_ENDPOINT = 'https://mcp.socket.dev/';
3
+ const DEFAULT_TIMEOUT = 15_000;
4
+ const MCP_PROTOCOL_VERSION = '2024-11-05';
5
+ function post(url, body, headers, timeoutMs) {
6
+ return new Promise((resolve, reject) => {
7
+ const parsed = new URL(url);
8
+ const req = https.request({
9
+ hostname: parsed.hostname,
10
+ port: parsed.port || 443,
11
+ path: parsed.pathname,
12
+ method: 'POST',
13
+ headers: { 'Content-Type': 'application/json', 'Content-Length': String(Buffer.byteLength(body)), ...headers },
14
+ timeout: timeoutMs,
15
+ }, res => {
16
+ let data = '';
17
+ res.on('data', (c) => data += c.toString());
18
+ res.on('end', () => {
19
+ const h = {};
20
+ for (const [k, v] of Object.entries(res.headers)) {
21
+ if (typeof v === 'string')
22
+ h[k] = v;
23
+ }
24
+ resolve({ status: res.statusCode ?? 0, headers: h, body: data });
25
+ });
26
+ });
27
+ req.on('timeout', () => { req.destroy(); reject(new Error('Socket MCP request timed out')); });
28
+ req.on('error', reject);
29
+ req.write(body);
30
+ req.end();
31
+ });
32
+ }
33
+ function parseScoresFromText(text) {
34
+ const results = [];
35
+ for (const line of text.split('\n')) {
36
+ if (!line.startsWith('pkg:'))
37
+ continue;
38
+ const match = line.match(/^(pkg:\w+\/[^@]+)@([^:]+):\s*(.+)/);
39
+ if (!match)
40
+ continue;
41
+ const [, purl, version, rest] = match;
42
+ const scores = {};
43
+ for (const pair of rest.split(',')) {
44
+ const [k, v] = pair.trim().split(':').map(s => s.trim());
45
+ if (k && v)
46
+ scores[k] = parseInt(v, 10);
47
+ }
48
+ results.push({
49
+ purl: purl,
50
+ version: version,
51
+ supplyChain: scores['supplyChain'] ?? 0,
52
+ vulnerability: scores['vulnerability'] ?? 0,
53
+ quality: scores['quality'] ?? 0,
54
+ maintenance: scores['maintenance'] ?? 0,
55
+ license: scores['license'] ?? 0,
56
+ });
57
+ }
58
+ return results;
59
+ }
60
+ export async function querySocketScores(packages, options = {}) {
61
+ const endpoint = options.endpoint ?? DEFAULT_ENDPOINT;
62
+ const timeout = options.timeoutMs ?? DEFAULT_TIMEOUT;
63
+ // Step 1: MCP initialize
64
+ const initBody = JSON.stringify({
65
+ jsonrpc: '2.0',
66
+ method: 'initialize',
67
+ params: {
68
+ protocolVersion: MCP_PROTOCOL_VERSION,
69
+ capabilities: {},
70
+ clientInfo: { name: 'brainclaw-security', version: '1.0.0' },
71
+ },
72
+ id: 1,
73
+ });
74
+ const initRes = await post(endpoint, initBody, {}, timeout);
75
+ if (initRes.status !== 200) {
76
+ throw new Error(`Socket MCP initialize failed: HTTP ${initRes.status}`);
77
+ }
78
+ const sessionId = initRes.headers['mcp-session-id'];
79
+ if (!sessionId) {
80
+ throw new Error('Socket MCP did not return a session ID');
81
+ }
82
+ // Step 2: Send initialized notification
83
+ const notifBody = JSON.stringify({ jsonrpc: '2.0', method: 'notifications/initialized' });
84
+ await post(endpoint, notifBody, { 'mcp-session-id': sessionId }, timeout);
85
+ // Step 3: Call depscore
86
+ const callBody = JSON.stringify({
87
+ jsonrpc: '2.0',
88
+ method: 'tools/call',
89
+ params: {
90
+ name: 'depscore',
91
+ arguments: { packages },
92
+ },
93
+ id: 2,
94
+ });
95
+ const callRes = await post(endpoint, callBody, { 'mcp-session-id': sessionId }, timeout);
96
+ if (callRes.status !== 200) {
97
+ throw new Error(`Socket MCP depscore failed: HTTP ${callRes.status}`);
98
+ }
99
+ const parsed = JSON.parse(callRes.body);
100
+ if (parsed.error) {
101
+ throw new Error(`Socket MCP error: ${parsed.error.message ?? JSON.stringify(parsed.error)}`);
102
+ }
103
+ const content = parsed.result?.content;
104
+ if (!Array.isArray(content) || content.length === 0) {
105
+ throw new Error('Socket MCP returned empty content');
106
+ }
107
+ const text = content[0]?.text;
108
+ if (typeof text !== 'string') {
109
+ throw new Error('Socket MCP returned non-text content');
110
+ }
111
+ return parseScoresFromText(text);
112
+ }
113
+ //# sourceMappingURL=socket-client.js.map