@inneranimalmedia/agentsam-sdk 2.6.3 → 2.6.4

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 (233) hide show
  1. package/AGENTSAM.md +6 -0
  2. package/README.md +16 -1
  3. package/bin/agentsam +16 -1
  4. package/docs/BRAND_INTELLIGENCE.md +1 -1
  5. package/docs/architecture/AGENTSAM_DISTRIBUTION_OWNERSHIP.md +176 -0
  6. package/docs/architecture/AGENTSAM_GO_RUNTIME.md +282 -0
  7. package/docs/architecture/CODEBASEINDEX_GUIDED_PATH.md +202 -0
  8. package/docs/architecture/FS_E2E_CLOSURE_RECEIPT.md +59 -0
  9. package/docs/architecture/LOCAL_FS_AUTHORITY.md +38 -0
  10. package/docs/architecture/LOCAL_STUDIO_CLOUDFLARE_OAUTH.md +28 -0
  11. package/docs/architecture/PLAN_CLI_AND_LOCAL_STUDIO_DESKTOP.md +96 -0
  12. package/docs/architecture/SAM_ACTIVITY_RECOVERY_RECEIPT.md +39 -0
  13. package/docs/architecture/SAM_DECISION_WORK_RECEIPT.md +54 -0
  14. package/docs/architecture/SAM_KERNEL.md +181 -0
  15. package/docs/architecture/SAM_MACHINE_NORMALIZATION_PRECOMMIT_REPORT.md +208 -0
  16. package/docs/architecture/SLASH_SKILLS_PORTABLE.md +116 -0
  17. package/docs/architecture/fs-e2e-receipt.latest.json +42 -0
  18. package/docs/architecture/previews/codebaseindex-guided-path/CODEBASEINDEX_GUIDED_PATH.md +202 -0
  19. package/docs/architecture/previews/codebaseindex-guided-path/index.html +321 -0
  20. package/migrations/d1/0010_portable_tickets_memory.sql +140 -0
  21. package/migrations/d1/0011_agentsam_skill_v2.sql +185 -0
  22. package/migrations/d1/0011b_agentsam_skill_v2_cutover.sql +22 -0
  23. package/migrations/d1/0011c_agentsam_skill_v2_backfill.sql +76 -0
  24. package/migrations/d1/0011d_agentsam_skill_v2_retrieval_revisions.sql +50 -0
  25. package/migrations/d1/0012_agentsam_tools_required_seed.sql +67 -0
  26. package/migrations/d1/0013_identity_oauth_states.sql +15 -0
  27. package/migrations/d1/0014_auth_event_log.sql +20 -0
  28. package/migrations/d1/0015_identity_oauth_state_app_id.sql +3 -0
  29. package/migrations/d1/README_PORTABLE_CONTROL_PLANE.md +16 -0
  30. package/migrations/sqlite/agentsam_skill_retrieval.portable.sql +42 -0
  31. package/package.json +19 -4
  32. package/packages/agentsam-contracts/src/errors.ts +16 -0
  33. package/packages/agentsam-errors/src/envelope.js +53 -0
  34. package/packages/agentsam-errors/src/index.js +1 -0
  35. package/packages/agentsam-errors/src/recovery.js +301 -0
  36. package/packages/agentsam-knowledge/src/providers/index.js +18 -6
  37. package/packages/connectors/cloudflare/src/routes.js +9 -0
  38. package/packages/connectors/cloudflare/tests/connector.test.mjs +26 -1
  39. package/packages/identity/.agentsam/features/oauth-login-portal/agentsam.feature.json +1 -1
  40. package/packages/identity/.agentsam/features/oauth-login-portal/routes.json +11 -2
  41. package/packages/identity/docs/PORTABLE_IDENTITY_ARCHITECTURE.md +50 -0
  42. package/packages/identity/migrations/D1_SCHEMA_MAPPING.md +31 -0
  43. package/packages/identity/migrations/sqlite/001_identity_core.sql +99 -0
  44. package/packages/identity/migrations/sqlite/002_identity_oauth_client.sql +38 -0
  45. package/packages/identity/migrations/sqlite/003_identity_oauth_server.sql +64 -0
  46. package/packages/identity/package.json +2 -2
  47. package/packages/identity/src/adapters/cloudflare-d1/index.js +122 -22
  48. package/packages/identity/src/adapters/sqlite/index.js +319 -0
  49. package/packages/identity/src/app/verify-app.js +95 -0
  50. package/packages/identity/src/contracts/identity-store.js +115 -0
  51. package/packages/identity/src/contracts/route-ids.js +30 -0
  52. package/packages/identity/src/contracts/route-projection.js +218 -0
  53. package/packages/identity/src/contracts/routes.js +11 -0
  54. package/packages/identity/src/core/browser-paths.js +4 -5
  55. package/packages/identity/src/core/constants.js +13 -8
  56. package/packages/identity/src/core/session-policy.js +32 -0
  57. package/packages/identity/src/frontend/auth-portal/pages/login.html +10 -10
  58. package/packages/identity/src/frontend/auth-portal/pages/reset.html +3 -3
  59. package/packages/identity/src/frontend/auth-portal/pages/signup.html +3 -3
  60. package/packages/identity/src/frontend/auth-portal/preview/dashboard-stub.html +1 -1
  61. package/packages/identity/src/index.js +25 -0
  62. package/packages/identity/src/oauth/README.md +10 -4
  63. package/packages/identity/src/oauth/credentials.js +20 -13
  64. package/packages/identity/src/oauth/finalize-inbound.js +1 -1
  65. package/packages/identity/src/oauth/iam-platform.js +8 -7
  66. package/packages/identity/src/oauth/redirect-paths.js +27 -36
  67. package/packages/identity/src/server/identity-service.js +30 -13
  68. package/packages/identity/src/server/mount-policy.js +30 -0
  69. package/packages/identity/src/server/post-auth.js +79 -0
  70. package/packages/identity/src/server/worker-router.js +104 -72
  71. package/packages/identity/tests/finalize-inbound-oauth.test.mjs +6 -6
  72. package/packages/identity/tests/iam-provider.test.mjs +1 -1
  73. package/packages/identity/tests/identity-service.test.mjs +36 -5
  74. package/packages/identity/tests/oauth-credentials.test.mjs +3 -1
  75. package/packages/identity/tests/portable-identity-architecture.test.mjs +157 -0
  76. package/packages/identity/tests/session-routes-policy.test.mjs +21 -0
  77. package/packages/theme-church-site/package.json +2 -1
  78. package/packages/theme-church-site/src/index.js +1 -0
  79. package/packages/theme-companions-site/package.json +2 -1
  80. package/packages/theme-companions-site/src/index.js +1 -0
  81. package/packages/theme-floors-site/package.json +2 -1
  82. package/packages/theme-floors-site/src/index.js +1 -0
  83. package/packages/theme-fuelnfree-site/package.json +2 -1
  84. package/packages/theme-fuelnfree-site/src/index.js +1 -0
  85. package/packages/theme-handyman-site/package.json +2 -1
  86. package/packages/theme-handyman-site/src/index.js +1 -0
  87. package/packages/theme-insurance-site/package.json +2 -1
  88. package/packages/theme-insurance-site/src/index.js +1 -0
  89. package/packages/theme-shinshu-site/package.json +2 -1
  90. package/packages/theme-shinshu-site/src/index.js +1 -0
  91. package/protocol/apps/agentsam.app.v1.schema.json +51 -0
  92. package/protocol/brand/brandpack.v1.schema.json +43 -0
  93. package/protocol/credentials/issue.v1.schema.json +38 -0
  94. package/protocol/database/connection.v1.schema.json +41 -0
  95. package/protocol/embeddings/embedding-profile.v1.schema.json +20 -0
  96. package/protocol/errors/error-envelope.schema.json +135 -1
  97. package/protocol/errors/recovery.v1.schema.json +50 -0
  98. package/protocol/runtime/workspace-fs.v1.schema.json +71 -0
  99. package/protocol/sam/activity.v1.schema.json +48 -0
  100. package/protocol/sam/answer.v1.schema.json +35 -0
  101. package/protocol/sam/calibration.v1.schema.json +21 -0
  102. package/protocol/sam/decision-receipt.v1.schema.json +29 -0
  103. package/protocol/sam/evaluation.v1.schema.json +19 -0
  104. package/protocol/sam/operation.schema.json +66 -0
  105. package/protocol/sam/outcome.v1.schema.json +36 -0
  106. package/protocol/sam/question.v1.schema.json +26 -0
  107. package/protocol/sam/registry.seed.json +153 -0
  108. package/protocol/sam/result.schema.json +53 -0
  109. package/protocol/sam/state.v1.schema.json +23 -0
  110. package/protocol/skills/agentsam.interaction.v1.schema.json +50 -0
  111. package/protocol/skills/agentsam.skill.v1.schema.json +57 -0
  112. package/protocol/ui/icon-registry.mjs +226 -0
  113. package/protocol/ui/icon.v1.schema.json +51 -0
  114. package/skills/README.md +22 -9
  115. package/skills/agentsam-codebaseindex/SKILL.md +225 -0
  116. package/skills/catalog.json +14 -0
  117. package/src/cli/command-catalog.js +130 -0
  118. package/src/cli/dispatch.js +48 -0
  119. package/src/cli.js +43 -1
  120. package/src/commands/api-key.js +244 -0
  121. package/src/commands/app.js +60 -28
  122. package/src/commands/brand.js +17 -19
  123. package/src/commands/codebaseindex.js +688 -0
  124. package/src/commands/env.js +152 -25
  125. package/src/commands/go.js +366 -53
  126. package/src/commands/interaction-clack.js +115 -0
  127. package/src/commands/models.js +1 -1
  128. package/src/commands/providers.js +62 -14
  129. package/src/commands/shell.js +43 -2
  130. package/src/commands/skill.js +248 -0
  131. package/src/commands/skills.js +1 -1
  132. package/src/commands/start-local.js +4 -0
  133. package/src/commands/whoami.js +90 -18
  134. package/src/go/build.js +229 -39
  135. package/src/go/cloudflare.js +506 -105
  136. package/src/go/container.js +120 -0
  137. package/src/go/contract.js +9 -4
  138. package/src/go/discover.js +149 -33
  139. package/src/go/index.js +15 -3
  140. package/src/go/native-probe-runner.mjs +119 -0
  141. package/src/go/{registry.js → official-registry.js} +64 -7
  142. package/src/go/official-release.js +10 -0
  143. package/src/go/receipts.js +77 -10
  144. package/src/go/verify.js +9 -2
  145. package/src/index.js +27 -0
  146. package/src/indexing/ingest/discover-models.js +298 -0
  147. package/src/indexing/ingest/inventory.js +243 -0
  148. package/src/indexing/ingest/job-graph.js +181 -0
  149. package/src/indexing/ingest/materials.js +210 -0
  150. package/src/lib/provider-credentials.js +63 -21
  151. package/src/lib/slash-commands.js +1 -0
  152. package/src/local-fs/capability.js +121 -0
  153. package/src/local-fs/freshness.js +75 -0
  154. package/src/local-fs/index.js +385 -0
  155. package/src/local-fs/paths.js +100 -0
  156. package/src/local-pty/server.js +295 -19
  157. package/src/mcp/client.js +2 -2
  158. package/src/models/ai-access-onboarding.js +112 -0
  159. package/src/models/discovery.js +10 -2
  160. package/src/models/inventory-core.js +9 -1
  161. package/src/sam/activity/index.js +183 -0
  162. package/src/sam/client.js +252 -0
  163. package/src/sam/decision/calibration.js +109 -0
  164. package/src/sam/decision/confidence.js +126 -0
  165. package/src/sam/decision/evaluate.js +157 -0
  166. package/src/sam/decision/evaluators/deterministic.js +341 -0
  167. package/src/sam/decision/evaluators/heuristic.js +61 -0
  168. package/src/sam/decision/evaluators/select.js +50 -0
  169. package/src/sam/decision/evaluators/semantic.js +149 -0
  170. package/src/sam/decision/hierarchical.js +61 -0
  171. package/src/sam/decision/index.js +53 -0
  172. package/src/sam/decision/policy.js +86 -0
  173. package/src/sam/decision/questions.js +120 -0
  174. package/src/sam/decision/receipt.js +148 -0
  175. package/src/sam/decision/state.js +117 -0
  176. package/src/sam/decision/types.js +22 -0
  177. package/src/sam/decision/validate.js +200 -0
  178. package/src/sam/define.js +51 -0
  179. package/src/sam/index.js +65 -0
  180. package/src/sam/operations/brand-scan.js +62 -0
  181. package/src/sam/operations/cad-blender-inspect.js +36 -0
  182. package/src/sam/operations/codebaseindex-ingest.js +49 -0
  183. package/src/sam/operations/decision-evaluate.js +59 -0
  184. package/src/sam/operations/planning-astar.js +77 -0
  185. package/src/sam/operations/planning-goap.js +60 -0
  186. package/src/sam/operations/repository-inspect.js +72 -0
  187. package/src/sam/operations/security-scan.js +33 -0
  188. package/src/sam/operations/terminal-exec.js +29 -0
  189. package/src/sam/planning/astar.js +311 -0
  190. package/src/sam/planning/goap.js +177 -0
  191. package/src/sam/planning/index.js +21 -0
  192. package/src/sam/planning/state.js +84 -0
  193. package/src/sam/registry.js +48 -0
  194. package/src/sam/result.js +77 -0
  195. package/src/sam/seed.js +44 -0
  196. package/src/sam/types.js +91 -0
  197. package/src/skills/catalog.js +64 -0
  198. package/src/skills/content-resolver.js +124 -0
  199. package/src/skills/hosted-store.js +37 -0
  200. package/src/skills/index.js +29 -64
  201. package/src/skills/interaction.js +102 -0
  202. package/src/skills/local-store.js +228 -0
  203. package/src/skills/manifest.js +104 -0
  204. package/src/skills/metrics.js +31 -0
  205. package/src/skills/registry.js +184 -0
  206. package/src/skills/runtime.js +287 -0
  207. package/src/skills/slash.js +44 -0
  208. package/src/ui/cli/help.js +94 -101
  209. package/test/cli/api-key-env-whoami.test.mjs +129 -0
  210. package/test/cli/codebaseindex-plan-ux.test.mjs +37 -0
  211. package/test/cli/go.test.mjs +62 -5
  212. package/test/cli/skill-npm-and-env.test.mjs +52 -0
  213. package/test/cli/wireframes-go-registry.test.mjs +87 -2
  214. package/test/go/build-source-identity.test.mjs +31 -0
  215. package/test/go/cloudflare-probe.test.mjs +274 -10
  216. package/test/go/distribution.test.mjs +28 -0
  217. package/test/integration/ai-access-onboarding.test.mjs +47 -0
  218. package/test/integration/cli-help.test.mjs +1 -1
  219. package/test/integration/cms-site-tenancy-contract.test.mjs +6 -6
  220. package/test/integration/codebaseindex-ingest.test.mjs +166 -0
  221. package/test/integration/icon-registry.test.mjs +67 -0
  222. package/test/integration/ingest-discover-models.test.mjs +30 -0
  223. package/test/integration/install-script.test.mjs +12 -9
  224. package/test/integration/local-fs.test.mjs +113 -0
  225. package/test/integration/provider-env-cli.test.mjs +2 -1
  226. package/test/integration/sam-activity-recovery.test.mjs +147 -0
  227. package/test/integration/sam-decision.test.mjs +584 -0
  228. package/test/integration/sam-kernel.test.mjs +99 -0
  229. package/test/integration/sam-planning-astar.test.mjs +279 -0
  230. package/test/integration/skill-runtime.test.mjs +240 -0
  231. package/test/integration/studio-fs-pty-e2e.test.mjs +294 -0
  232. package/test/models.test.mjs +14 -7
  233. package/test/shell.test.mjs +4 -4
@@ -0,0 +1,243 @@
1
+ /**
2
+ * Inventory projection for codebaseindex — structured authority + compact ASCII view.
3
+ * The ASCII tree is a projection, not the source of truth.
4
+ *
5
+ * Scope suggestions categorize paths; they never auto-apply exclusions.
6
+ */
7
+
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+
11
+ const EXT_LANG = new Map([
12
+ ['.js', 'JavaScript'], ['.mjs', 'JavaScript'], ['.cjs', 'JavaScript'],
13
+ ['.ts', 'TypeScript'], ['.tsx', 'TypeScript'], ['.jsx', 'JavaScript'],
14
+ ['.py', 'Python'], ['.go', 'Go'], ['.rs', 'Rust'], ['.md', 'Markdown'],
15
+ ['.json', 'JSON'], ['.css', 'CSS'], ['.html', 'HTML'], ['.htm', 'HTML'],
16
+ ['.yml', 'YAML'], ['.yaml', 'YAML'], ['.sql', 'SQL'], ['.sh', 'Shell'],
17
+ ]);
18
+
19
+ const IMAGE = new Set(['.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg', '.ico']);
20
+ const MODEL3D = new Set(['.glb', '.gltf', '.obj', '.fbx', '.stl']);
21
+ const ARCHIVE = new Set(['.zip', '.tar', '.tgz', '.gz']);
22
+
23
+ /** Usually exclude — dependency / build caches (candidates only). */
24
+ const CATEGORY_DEPENDENCIES = new Set([
25
+ 'node_modules', '.git', 'dist', 'build', '.next', 'coverage', '.turbo',
26
+ '.cache', 'out', 'target', '__pycache__', '.venv', 'venv', '.wrangler',
27
+ ]);
28
+
29
+ /** Primary source candidates. */
30
+ const CATEGORY_SOURCE = new Set([
31
+ 'src', 'apps', 'packages', 'protocol', 'python', 'services', 'lib', 'server',
32
+ 'worker', 'workers', 'runtime', 'cmd', 'internal', 'pkg',
33
+ ]);
34
+
35
+ /** Documentation / examples. */
36
+ const CATEGORY_DOCS = new Set([
37
+ 'docs', 'examples', 'guides', 'handbook', 'README', 'changelog',
38
+ ]);
39
+
40
+ /** Tooling / CI — review before including. */
41
+ const CATEGORY_TOOLING = new Set([
42
+ 'bin', 'scripts', 'tools', '.github', '.changeset', '.husky', '.vscode',
43
+ '.cursor', 'tags', 'test', 'tests', '__tests__', 'spec', 'e2e',
44
+ ]);
45
+
46
+ /** Generated / historical — review before including. */
47
+ const CATEGORY_GENERATED = new Set([
48
+ 'generated', 'fixtures', 'snapshots', 'artifacts', 'public', 'static',
49
+ 'site', 'sites', 'templates', 'level-1', 'focus-timer',
50
+ ]);
51
+
52
+ /** AgentSam / config control plane — review. */
53
+ const CATEGORY_CONFIG = new Set([
54
+ '.agentsam', 'migrations', 'registry', 'wrangler', 'deploy',
55
+ ]);
56
+
57
+ const SUGGEST_EXCLUDE_NAMES = CATEGORY_DEPENDENCIES;
58
+
59
+ function classifyTopLevel(name) {
60
+ if (CATEGORY_DEPENDENCIES.has(name)) return 'dependencies';
61
+ if (CATEGORY_SOURCE.has(name)) return 'source';
62
+ if (CATEGORY_DOCS.has(name)) return 'docs';
63
+ if (CATEGORY_GENERATED.has(name)) return 'generated';
64
+ if (CATEGORY_CONFIG.has(name)) return 'config';
65
+ if (CATEGORY_TOOLING.has(name)) return 'tooling';
66
+ if (/^(old|legacy|archive|backup|tmp|temp)/i.test(name)) return 'historical';
67
+ if (name.startsWith('.')) return 'config';
68
+ return 'unknown';
69
+ }
70
+
71
+ function walk(root, rel = '', acc = [], depth = 0) {
72
+ if (depth > 6 || acc.length > 8000) return acc;
73
+ const abs = rel ? path.join(root, rel) : root;
74
+ let entries;
75
+ try { entries = fs.readdirSync(abs, { withFileTypes: true }); }
76
+ catch { return acc; }
77
+ for (const ent of entries) {
78
+ if (ent.name === '.git' || ent.name === 'node_modules') continue;
79
+ const child = rel ? `${rel}/${ent.name}` : ent.name;
80
+ const full = path.join(root, child);
81
+ if (ent.isDirectory()) {
82
+ acc.push({ path: child, kind: 'directory', name: ent.name });
83
+ if (!SUGGEST_EXCLUDE_NAMES.has(ent.name)) walk(root, child, acc, depth + 1);
84
+ } else if (ent.isFile()) {
85
+ let bytes = 0;
86
+ try { bytes = fs.statSync(full).size; } catch { /* ignore */ }
87
+ const ext = path.extname(ent.name).toLowerCase();
88
+ acc.push({
89
+ path: child,
90
+ kind: 'file',
91
+ name: ent.name,
92
+ ext,
93
+ bytes,
94
+ language: EXT_LANG.get(ext) || null,
95
+ media: IMAGE.has(ext) ? 'image' : MODEL3D.has(ext) ? 'model3d' : ARCHIVE.has(ext) ? 'archive' : null,
96
+ });
97
+ }
98
+ }
99
+ return acc;
100
+ }
101
+
102
+ function countLoc(root, filePath, maxBytes = 256_000) {
103
+ try {
104
+ const full = path.join(root, filePath);
105
+ const st = fs.statSync(full);
106
+ if (st.size > maxBytes) return 0;
107
+ const text = fs.readFileSync(full, 'utf8');
108
+ if (text.includes('\0')) return 0;
109
+ return text.split(/\r?\n/).length;
110
+ } catch {
111
+ return 0;
112
+ }
113
+ }
114
+
115
+ /**
116
+ * @param {{ root: string, materials?: object|null, maxFiles?: number }} opts
117
+ */
118
+ export function buildInventory(opts) {
119
+ const root = path.resolve(opts.root);
120
+ const entries = walk(root);
121
+ const files = entries.filter((e) => e.kind === 'file');
122
+ const dirs = entries.filter((e) => e.kind === 'directory');
123
+ const top = dirs.filter((d) => !d.path.includes('/')).map((d) => d.name);
124
+ const languages = {};
125
+ let locTotal = 0;
126
+ for (const f of files.slice(0, opts.maxFiles || 4000)) {
127
+ if (!f.language) continue;
128
+ const loc = countLoc(root, f.path);
129
+ languages[f.language] = (languages[f.language] || 0) + loc;
130
+ locTotal += loc;
131
+ }
132
+ const images = files.filter((f) => f.media === 'image').length;
133
+ const models3d = files.filter((f) => f.media === 'model3d').length;
134
+ const archives = files.filter((f) => f.media === 'archive').length;
135
+ const packageManifests = files.filter((f) => f.name === 'package.json').length;
136
+
137
+ /** @type {Record<string, string[]>} */
138
+ const categories = {
139
+ source: [],
140
+ docs: [],
141
+ config: [],
142
+ generated: [],
143
+ dependencies: [],
144
+ tooling: [],
145
+ historical: [],
146
+ unknown: [],
147
+ };
148
+ for (const name of top) {
149
+ categories[classifyTopLevel(name)].push(name);
150
+ }
151
+
152
+ const primaryInclude = [
153
+ ...categories.source,
154
+ ...categories.docs,
155
+ ];
156
+ const reviewInclude = [
157
+ ...categories.config,
158
+ ...categories.generated,
159
+ ...categories.tooling.filter((n) => n === 'test' || n === 'tests' || n === 'scripts'),
160
+ ];
161
+ const usuallyExclude = [
162
+ ...categories.dependencies,
163
+ ...categories.tooling.filter((n) => !reviewInclude.includes(n) && n !== 'scripts' && n !== 'test' && n !== 'tests'),
164
+ ...categories.historical,
165
+ ];
166
+
167
+ // Default starting include: primary source + docs (not "everything").
168
+ // Exclusions are candidates only — never auto-applied without user confirm.
169
+ const suggestedInclude = (primaryInclude.length ? primaryInclude : top.filter((n) => !CATEGORY_DEPENDENCIES.has(n))).slice(0, 24);
170
+ if (!suggestedInclude.length) suggestedInclude.push('.');
171
+ const suggestedExclude = [...new Set(usuallyExclude)];
172
+
173
+ /** @type {object} */
174
+ const inventory = {
175
+ schema: 'agentsam.inventory.v1',
176
+ root,
177
+ scanned_at: new Date().toISOString(),
178
+ counts: {
179
+ files: files.length,
180
+ directories: dirs.length,
181
+ loc_sampled: locTotal,
182
+ images,
183
+ models3d,
184
+ archives,
185
+ package_manifests: packageManifests,
186
+ },
187
+ languages,
188
+ top_level: top,
189
+ categories,
190
+ suggested: {
191
+ include: suggestedInclude,
192
+ exclude: suggestedExclude,
193
+ review: reviewInclude,
194
+ note: 'Categories are machine inventory. Suggestions are advisory — no exclusions applied automatically. User confirmations are authoritative.',
195
+ },
196
+ materials: opts.materials || null,
197
+ sample_paths: files.slice(0, 40).map((f) => f.path),
198
+ };
199
+ return inventory;
200
+ }
201
+
202
+ /**
203
+ * Compact human projection of inventory (not authority).
204
+ * @param {ReturnType<typeof buildInventory>} inventory
205
+ */
206
+ export function formatInventoryTree(inventory) {
207
+ const lines = [];
208
+ lines.push(`repository ${inventory.root}`);
209
+ lines.push(`files ${inventory.counts.files} · dirs ${inventory.counts.directories} · LOC~${inventory.counts.loc_sampled}`);
210
+ lines.push('');
211
+ for (const name of inventory.top_level.slice(0, 24)) {
212
+ const cat = inventory.categories
213
+ ? Object.entries(inventory.categories).find(([, names]) => names.includes(name))?.[0]
214
+ : null;
215
+ const mark = cat ? ` · ${cat}` : '';
216
+ lines.push(`├── ${name}/${mark}`);
217
+ }
218
+ if (inventory.top_level.length > 24) lines.push('└── …');
219
+ lines.push('');
220
+ lines.push('Languages');
221
+ const langs = Object.entries(inventory.languages || {}).sort((a, b) => b[1] - a[1]);
222
+ for (const [lang, loc] of langs.slice(0, 8)) {
223
+ lines.push(` ${lang.padEnd(14)} ${String(loc).padStart(8)} LOC`);
224
+ }
225
+ lines.push('');
226
+ lines.push('Detected');
227
+ lines.push(` ${inventory.counts.package_manifests} package.json`);
228
+ lines.push(` ${inventory.counts.images} images · ${inventory.counts.models3d} 3D · ${inventory.counts.archives} archives`);
229
+ lines.push('');
230
+ lines.push('Recommended scope candidates');
231
+ lines.push(` Primary source ${(inventory.categories?.source || []).join(', ') || '(none)'}`);
232
+ lines.push(` Documentation ${(inventory.categories?.docs || []).join(', ') || '(none)'}`);
233
+ lines.push(` Review before incl ${(inventory.suggested?.review || []).join(', ') || '(none)'}`);
234
+ lines.push(` Usually exclude ${(inventory.suggested?.exclude || []).join(', ') || '(none)'}`);
235
+ lines.push(' (No exclusions applied automatically.)');
236
+ lines.push('');
237
+ lines.push('Starting suggestion (editable)');
238
+ lines.push(` include: ${(inventory.suggested.include || []).join(', ') || '(none)'}`);
239
+ lines.push(` exclude: ${(inventory.suggested.exclude || []).join(', ') || '(none)'}`);
240
+ return lines.join('\n');
241
+ }
242
+
243
+ export { classifyTopLevel };
@@ -0,0 +1,181 @@
1
+ /**
2
+ * codebaseindex.job.graph.v1 — portable job graph for CLI / Gantt / Studio.
3
+ */
4
+
5
+ import { randomUUID } from 'node:crypto';
6
+
7
+ export const JOB_GRAPH_SCHEMA = 'codebaseindex.job.graph.v1';
8
+
9
+ const DEFAULT_NODES = [
10
+ 'material.stage',
11
+ 'repository.snapshot',
12
+ 'inventory.classify',
13
+ 'scope.resolve',
14
+ 'profile.resolve',
15
+ 'lane.resolve',
16
+ 'plan.dry_run',
17
+ 'ast.parse',
18
+ 'chunks.build',
19
+ 'embedding.generate',
20
+ 'storage.write',
21
+ 'generation.verify',
22
+ 'search.smoke',
23
+ ];
24
+
25
+ /**
26
+ * @param {object} [opts]
27
+ */
28
+ export function createCodebaseindexJobGraph(opts = {}) {
29
+ const id = opts.id || `cidxjob_${randomUUID().replace(/-/g, '').slice(0, 16)}`;
30
+ const now = new Date().toISOString();
31
+ const nodes = (opts.nodes || DEFAULT_NODES).map((nodeId, index) => ({
32
+ id: nodeId,
33
+ status: opts.completed?.includes(nodeId) ? 'done'
34
+ : opts.current === nodeId ? 'run'
35
+ : 'pending',
36
+ ordinal: index,
37
+ started_at: null,
38
+ completed_at: null,
39
+ inputs: {},
40
+ outputs: {},
41
+ errors: [],
42
+ }));
43
+ return {
44
+ schema: JOB_GRAPH_SCHEMA,
45
+ id,
46
+ pipeline: 'sam.codebaseindex.index.run',
47
+ operation: 'codebaseindex.ingest',
48
+ created_at: now,
49
+ updated_at: now,
50
+ status: opts.status || 'planned',
51
+ current: nodes.find((node) => node.status === 'run')?.id || null,
52
+ nodes,
53
+ work_items: nodes.map((n) => ({
54
+ id: n.id,
55
+ title: n.id,
56
+ status: n.status === 'done' ? 'completed' : n.status === 'run' ? 'in_progress' : 'pending',
57
+ start: null,
58
+ end: null,
59
+ })),
60
+ };
61
+ }
62
+
63
+ /**
64
+ * Mark nodes through `throughId` as done; set next as run (or complete).
65
+ * @param {ReturnType<typeof createCodebaseindexJobGraph>} graph
66
+ * @param {string} throughId
67
+ */
68
+ export function advanceJobGraph(graph, throughId) {
69
+ const next = structuredClone(graph);
70
+ next.updated_at = new Date().toISOString();
71
+ let found = false;
72
+ for (const node of next.nodes) {
73
+ if (!found) {
74
+ if (node.status !== 'skipped') {
75
+ node.status = 'done';
76
+ node.completed_at = next.updated_at;
77
+ }
78
+ if (node.id === throughId) found = true;
79
+ } else if (node.status === 'pending') {
80
+ node.status = 'run';
81
+ node.started_at = next.updated_at;
82
+ break;
83
+ }
84
+ }
85
+ syncWorkItems(next);
86
+ const allDone = next.nodes.every((n) => n.status === 'done' || n.status === 'skipped');
87
+ next.status = allDone ? 'completed' : 'running';
88
+ next.current = next.nodes.find((n) => n.status === 'run')?.id || null;
89
+ return next;
90
+ }
91
+
92
+ /**
93
+ * After a successful dry-run: freeze remaining nodes as planned (not "run").
94
+ * Prevents `→ ast.parse` looking like the CLI is waiting for another command.
95
+ *
96
+ * @param {ReturnType<typeof createCodebaseindexJobGraph>} graph
97
+ * @param {{ skipEmbedding?: boolean }} [opts]
98
+ */
99
+ export function freezePlanJobGraph(graph, opts = {}) {
100
+ const next = structuredClone(graph);
101
+ next.updated_at = new Date().toISOString();
102
+ next.status = 'planned';
103
+ for (const node of next.nodes) {
104
+ if (node.status === 'done') continue;
105
+ if (opts.skipEmbedding && node.id === 'embedding.generate') {
106
+ node.status = 'skipped';
107
+ node.completed_at = next.updated_at;
108
+ continue;
109
+ }
110
+ // Clear accidental "run" pointer from advanceJobGraph
111
+ node.status = 'planned';
112
+ node.started_at = null;
113
+ }
114
+ next.current = null;
115
+ syncWorkItems(next);
116
+ return next;
117
+ }
118
+
119
+ /**
120
+ * Mark a graph node as intentionally skipped while preserving graph truth.
121
+ */
122
+ export function skipJobGraphNode(graph, nodeId, reason = 'not_required') {
123
+ const next = structuredClone(graph);
124
+ next.updated_at = new Date().toISOString();
125
+
126
+ const node = next.nodes.find((candidate) => candidate.id === nodeId);
127
+ if (!node) throw new Error(`Unknown job graph node: ${nodeId}`);
128
+
129
+ node.status = 'skipped';
130
+ node.started_at = null;
131
+ node.completed_at = next.updated_at;
132
+ node.outputs = { ...(node.outputs || {}), skip_reason: reason };
133
+
134
+ syncWorkItems(next);
135
+
136
+ const allDone = next.nodes.every(
137
+ (candidate) => candidate.status === 'done' || candidate.status === 'skipped',
138
+ );
139
+
140
+ next.status = allDone ? 'completed' : 'running';
141
+ next.current = next.nodes.find((candidate) => candidate.status === 'run')?.id || null;
142
+
143
+ return next;
144
+ }
145
+
146
+ /**
147
+ * Human projection of the job graph for CLI notes.
148
+ * @param {ReturnType<typeof createCodebaseindexJobGraph>} graph
149
+ * @param {{ planOnly?: boolean }} [opts]
150
+ */
151
+ export function formatJobGraphHuman(graph, opts = {}) {
152
+ const lines = [];
153
+ const planned = [];
154
+ for (const n of graph.nodes || []) {
155
+ if (n.status === 'done') lines.push(`✓ ${n.id}`);
156
+ else if (n.status === 'skipped') lines.push(`⊘ ${n.id} skipped`);
157
+ else if (opts.planOnly || n.status === 'planned' || graph.status === 'planned') {
158
+ planned.push(n.id);
159
+ } else if (n.status === 'run') lines.push(`→ ${n.id}`);
160
+ else lines.push(`· ${n.id}`);
161
+ }
162
+ if (planned.length) {
163
+ lines.push('');
164
+ lines.push('PLANNED FOR RUN');
165
+ for (const id of planned) lines.push(`○ ${id}`);
166
+ }
167
+ return lines.join('\n');
168
+ }
169
+
170
+ function syncWorkItems(graph) {
171
+ graph.work_items = graph.nodes.map((n) => ({
172
+ id: n.id,
173
+ title: n.id,
174
+ status: n.status === 'done' || n.status === 'skipped' ? 'completed'
175
+ : n.status === 'run' ? 'in_progress'
176
+ : n.status === 'planned' ? 'planned'
177
+ : 'pending',
178
+ start: n.started_at,
179
+ end: n.completed_at,
180
+ }));
181
+ }
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Material intake for codebaseindex.ingest — paths pasted/dropped into the CLI.
3
+ * Handles archives (tar/zip), sites/builds, HTML, images, GLB, and mixed trees.
4
+ */
5
+
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+ import { createHash, randomUUID } from 'node:crypto';
9
+ import { execFileSync, spawnSync } from 'node:child_process';
10
+
11
+ const ARCHIVE_EXT = new Set(['.zip', '.tar', '.tgz', '.gz', '.tar.gz', '.tbz2', '.tar.bz2']);
12
+ const IMAGE_EXT = new Set(['.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg', '.ico', '.bmp', '.avif']);
13
+ const MODEL_3D_EXT = new Set(['.glb', '.gltf', '.obj', '.fbx', '.stl', '.usdz']);
14
+ const DOC_EXT = new Set(['.html', '.htm', '.md', '.txt', '.css', '.scss', '.json', '.xml', '.csv']);
15
+ const CODE_EXT = new Set([
16
+ '.js', '.mjs', '.cjs', '.ts', '.tsx', '.jsx', '.py', '.go', '.rs', '.java', '.kt',
17
+ '.swift', '.c', '.h', '.cpp', '.cc', '.cs', '.rb', '.php', '.sql', '.sh', '.bash',
18
+ '.zsh', '.yaml', '.yml', '.toml', '.vue', '.svelte',
19
+ ]);
20
+
21
+ function clean(value) {
22
+ return value == null ? '' : String(value).trim();
23
+ }
24
+
25
+ function extOf(filePath) {
26
+ const lower = filePath.toLowerCase();
27
+ if (lower.endsWith('.tar.gz')) return '.tar.gz';
28
+ if (lower.endsWith('.tar.bz2')) return '.tar.bz2';
29
+ return path.extname(lower);
30
+ }
31
+
32
+ /**
33
+ * Parse pasted CLI text into filesystem paths (one per line / whitespace-separated quoted).
34
+ * Terminal drag-drop typically pastes absolute paths; multi-line paste is supported.
35
+ * @param {string} text
36
+ * @returns {string[]}
37
+ */
38
+ export function parsePastedPaths(text) {
39
+ const raw = clean(text);
40
+ if (!raw) return [];
41
+ const paths = [];
42
+ const re = /"([^"]+)"|'([^']+)'|(`([^`]+)`)|(\S+)/g;
43
+ let match;
44
+ while ((match = re.exec(raw))) {
45
+ const candidate = match[1] || match[2] || match[4] || match[5];
46
+ if (!candidate || candidate.startsWith('-')) continue;
47
+ paths.push(candidate.replace(/\\ /g, ' '));
48
+ }
49
+ return [...new Set(paths)];
50
+ }
51
+
52
+ /**
53
+ * @param {string} filePath
54
+ */
55
+ export function classifyMaterial(filePath) {
56
+ const resolved = path.resolve(filePath);
57
+ const ext = extOf(resolved);
58
+ const exists = fs.existsSync(resolved);
59
+ const stat = exists ? fs.statSync(resolved) : null;
60
+ let kind = 'unknown';
61
+ if (stat?.isDirectory()) kind = 'directory';
62
+ else if (ARCHIVE_EXT.has(ext) || resolved.toLowerCase().endsWith('.tar.gz')) kind = 'archive';
63
+ else if (IMAGE_EXT.has(ext)) kind = 'image';
64
+ else if (MODEL_3D_EXT.has(ext)) kind = 'model3d';
65
+ else if (DOC_EXT.has(ext)) kind = 'document';
66
+ else if (CODE_EXT.has(ext)) kind = 'code';
67
+ else if (stat?.isFile()) kind = 'file';
68
+ return {
69
+ path: resolved,
70
+ exists,
71
+ kind,
72
+ ext,
73
+ bytes: stat?.isFile() ? stat.size : null,
74
+ sha256: stat?.isFile() && stat.size <= 32 * 1024 * 1024 ? hashFile(resolved) : null,
75
+ };
76
+ }
77
+
78
+ function hashFile(filePath) {
79
+ try {
80
+ return createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');
81
+ } catch {
82
+ return null;
83
+ }
84
+ }
85
+
86
+ function hasBin(name) {
87
+ const result = spawnSync(name, ['--help'], { stdio: 'ignore' });
88
+ // Some tools exit non-zero on --help; presence of spawn failure matters more.
89
+ return result.error == null;
90
+ }
91
+
92
+ /**
93
+ * Extract archive into destDir. Uses host tar/unzip (no extra npm deps).
94
+ * @param {string} archivePath
95
+ * @param {string} destDir
96
+ */
97
+ export function extractArchive(archivePath, destDir) {
98
+ fs.mkdirSync(destDir, { recursive: true });
99
+ const lower = archivePath.toLowerCase();
100
+ if (lower.endsWith('.zip')) {
101
+ if (!hasBin('unzip')) throw new Error('unzip_unavailable: install unzip to extract .zip materials');
102
+ execFileSync('unzip', ['-q', '-o', archivePath, '-d', destDir], { stdio: ['ignore', 'pipe', 'pipe'] });
103
+ return { tool: 'unzip', dest: destDir };
104
+ }
105
+ if (lower.endsWith('.tar') || lower.endsWith('.tar.gz') || lower.endsWith('.tgz')
106
+ || lower.endsWith('.tar.bz2') || lower.endsWith('.tbz2') || lower.endsWith('.gz')) {
107
+ if (!hasBin('tar')) throw new Error('tar_unavailable: install tar to extract archive materials');
108
+ execFileSync('tar', ['-xf', archivePath, '-C', destDir], { stdio: ['ignore', 'pipe', 'pipe'] });
109
+ return { tool: 'tar', dest: destDir };
110
+ }
111
+ throw new Error(`unsupported_archive:${path.basename(archivePath)}`);
112
+ }
113
+
114
+ /**
115
+ * Stage pasted/dropped materials under `.agentsam/ingest/<id>/`.
116
+ * Archives are extracted; files/dirs are copied or linked by relative include paths.
117
+ *
118
+ * @param {{ root: string, materials: string[], ingestId?: string }} opts
119
+ */
120
+ export function stageMaterials(opts) {
121
+ const root = path.resolve(opts.root);
122
+ const ingestId = opts.ingestId || `ing_${randomUUID().replace(/-/g, '').slice(0, 12)}`;
123
+ const stageRoot = path.join(root, '.agentsam', 'ingest', ingestId);
124
+ fs.mkdirSync(stageRoot, { recursive: true });
125
+
126
+ /** @type {object[]} */
127
+ const items = [];
128
+ /** @type {string[]} */
129
+ const include = [];
130
+ /** @type {object[]} */
131
+ const assets = [];
132
+
133
+ for (const raw of opts.materials) {
134
+ const classified = classifyMaterial(raw);
135
+ if (!classified.exists) {
136
+ items.push({ ...classified, status: 'missing' });
137
+ continue;
138
+ }
139
+
140
+ if (classified.kind === 'archive') {
141
+ const dest = path.join(stageRoot, 'archives', path.basename(classified.path, classified.ext) || 'archive');
142
+ fs.mkdirSync(dest, { recursive: true });
143
+ const extracted = extractArchive(classified.path, dest);
144
+ const rel = path.relative(root, dest);
145
+ include.push(rel);
146
+ items.push({ ...classified, status: 'extracted', stage: dest, extract: extracted });
147
+ continue;
148
+ }
149
+
150
+ if (classified.kind === 'directory') {
151
+ const relInside = classified.path.startsWith(`${root}${path.sep}`) || classified.path === root
152
+ ? path.relative(root, classified.path) || '.'
153
+ : null;
154
+ if (relInside != null) {
155
+ include.push(relInside === '' ? '.' : relInside);
156
+ items.push({ ...classified, status: 'included', include: relInside || '.' });
157
+ } else {
158
+ const dest = path.join(stageRoot, 'trees', path.basename(classified.path) || 'tree');
159
+ copyTree(classified.path, dest);
160
+ const rel = path.relative(root, dest);
161
+ include.push(rel);
162
+ items.push({ ...classified, status: 'copied', stage: dest });
163
+ }
164
+ continue;
165
+ }
166
+
167
+ // Single file
168
+ const destDir = path.join(stageRoot, classified.kind === 'image' ? 'images'
169
+ : classified.kind === 'model3d' ? 'models3d'
170
+ : classified.kind === 'document' ? 'documents'
171
+ : 'files');
172
+ fs.mkdirSync(destDir, { recursive: true });
173
+ const dest = path.join(destDir, path.basename(classified.path));
174
+ fs.copyFileSync(classified.path, dest);
175
+ const rel = path.relative(root, dest);
176
+ include.push(path.dirname(rel));
177
+ assets.push({
178
+ kind: classified.kind,
179
+ path: rel,
180
+ bytes: classified.bytes,
181
+ sha256: classified.sha256,
182
+ ext: classified.ext,
183
+ });
184
+ items.push({ ...classified, status: 'staged', stage: dest, include: path.dirname(rel) });
185
+ }
186
+
187
+ const uniqueInclude = [...new Set(include.map((p) => p.replace(/\\/g, '/').replace(/\/$/, '') || '.'))].sort();
188
+ const manifest = {
189
+ schema: 'agentsam.ingest.materials.v1',
190
+ ingest_id: ingestId,
191
+ staged_at: new Date().toISOString(),
192
+ stage_root: path.relative(root, stageRoot),
193
+ items,
194
+ assets,
195
+ include: uniqueInclude,
196
+ };
197
+ fs.writeFileSync(path.join(stageRoot, 'manifest.json'), `${JSON.stringify(manifest, null, 2)}\n`);
198
+ return manifest;
199
+ }
200
+
201
+ function copyTree(src, dest) {
202
+ fs.mkdirSync(dest, { recursive: true });
203
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
204
+ if (entry.name === '.git' || entry.name === 'node_modules' || entry.name === '.agentsam') continue;
205
+ const from = path.join(src, entry.name);
206
+ const to = path.join(dest, entry.name);
207
+ if (entry.isDirectory()) copyTree(from, to);
208
+ else if (entry.isFile()) fs.copyFileSync(from, to);
209
+ }
210
+ }