@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.
- package/AGENTSAM.md +6 -0
- package/README.md +16 -1
- package/bin/agentsam +16 -1
- package/docs/BRAND_INTELLIGENCE.md +1 -1
- package/docs/architecture/AGENTSAM_DISTRIBUTION_OWNERSHIP.md +176 -0
- package/docs/architecture/AGENTSAM_GO_RUNTIME.md +282 -0
- package/docs/architecture/CODEBASEINDEX_GUIDED_PATH.md +202 -0
- package/docs/architecture/FS_E2E_CLOSURE_RECEIPT.md +59 -0
- package/docs/architecture/LOCAL_FS_AUTHORITY.md +38 -0
- package/docs/architecture/LOCAL_STUDIO_CLOUDFLARE_OAUTH.md +28 -0
- package/docs/architecture/PLAN_CLI_AND_LOCAL_STUDIO_DESKTOP.md +96 -0
- package/docs/architecture/SAM_ACTIVITY_RECOVERY_RECEIPT.md +39 -0
- package/docs/architecture/SAM_DECISION_WORK_RECEIPT.md +54 -0
- package/docs/architecture/SAM_KERNEL.md +181 -0
- package/docs/architecture/SAM_MACHINE_NORMALIZATION_PRECOMMIT_REPORT.md +208 -0
- package/docs/architecture/SLASH_SKILLS_PORTABLE.md +116 -0
- package/docs/architecture/fs-e2e-receipt.latest.json +42 -0
- package/docs/architecture/previews/codebaseindex-guided-path/CODEBASEINDEX_GUIDED_PATH.md +202 -0
- package/docs/architecture/previews/codebaseindex-guided-path/index.html +321 -0
- package/migrations/d1/0010_portable_tickets_memory.sql +140 -0
- package/migrations/d1/0011_agentsam_skill_v2.sql +185 -0
- package/migrations/d1/0011b_agentsam_skill_v2_cutover.sql +22 -0
- package/migrations/d1/0011c_agentsam_skill_v2_backfill.sql +76 -0
- package/migrations/d1/0011d_agentsam_skill_v2_retrieval_revisions.sql +50 -0
- package/migrations/d1/0012_agentsam_tools_required_seed.sql +67 -0
- package/migrations/d1/0013_identity_oauth_states.sql +15 -0
- package/migrations/d1/0014_auth_event_log.sql +20 -0
- package/migrations/d1/0015_identity_oauth_state_app_id.sql +3 -0
- package/migrations/d1/README_PORTABLE_CONTROL_PLANE.md +16 -0
- package/migrations/sqlite/agentsam_skill_retrieval.portable.sql +42 -0
- package/package.json +19 -4
- package/packages/agentsam-contracts/src/errors.ts +16 -0
- package/packages/agentsam-errors/src/envelope.js +53 -0
- package/packages/agentsam-errors/src/index.js +1 -0
- package/packages/agentsam-errors/src/recovery.js +301 -0
- package/packages/agentsam-knowledge/src/providers/index.js +18 -6
- package/packages/connectors/cloudflare/src/routes.js +9 -0
- package/packages/connectors/cloudflare/tests/connector.test.mjs +26 -1
- package/packages/identity/.agentsam/features/oauth-login-portal/agentsam.feature.json +1 -1
- package/packages/identity/.agentsam/features/oauth-login-portal/routes.json +11 -2
- package/packages/identity/docs/PORTABLE_IDENTITY_ARCHITECTURE.md +50 -0
- package/packages/identity/migrations/D1_SCHEMA_MAPPING.md +31 -0
- package/packages/identity/migrations/sqlite/001_identity_core.sql +99 -0
- package/packages/identity/migrations/sqlite/002_identity_oauth_client.sql +38 -0
- package/packages/identity/migrations/sqlite/003_identity_oauth_server.sql +64 -0
- package/packages/identity/package.json +2 -2
- package/packages/identity/src/adapters/cloudflare-d1/index.js +122 -22
- package/packages/identity/src/adapters/sqlite/index.js +319 -0
- package/packages/identity/src/app/verify-app.js +95 -0
- package/packages/identity/src/contracts/identity-store.js +115 -0
- package/packages/identity/src/contracts/route-ids.js +30 -0
- package/packages/identity/src/contracts/route-projection.js +218 -0
- package/packages/identity/src/contracts/routes.js +11 -0
- package/packages/identity/src/core/browser-paths.js +4 -5
- package/packages/identity/src/core/constants.js +13 -8
- package/packages/identity/src/core/session-policy.js +32 -0
- package/packages/identity/src/frontend/auth-portal/pages/login.html +10 -10
- package/packages/identity/src/frontend/auth-portal/pages/reset.html +3 -3
- package/packages/identity/src/frontend/auth-portal/pages/signup.html +3 -3
- package/packages/identity/src/frontend/auth-portal/preview/dashboard-stub.html +1 -1
- package/packages/identity/src/index.js +25 -0
- package/packages/identity/src/oauth/README.md +10 -4
- package/packages/identity/src/oauth/credentials.js +20 -13
- package/packages/identity/src/oauth/finalize-inbound.js +1 -1
- package/packages/identity/src/oauth/iam-platform.js +8 -7
- package/packages/identity/src/oauth/redirect-paths.js +27 -36
- package/packages/identity/src/server/identity-service.js +30 -13
- package/packages/identity/src/server/mount-policy.js +30 -0
- package/packages/identity/src/server/post-auth.js +79 -0
- package/packages/identity/src/server/worker-router.js +104 -72
- package/packages/identity/tests/finalize-inbound-oauth.test.mjs +6 -6
- package/packages/identity/tests/iam-provider.test.mjs +1 -1
- package/packages/identity/tests/identity-service.test.mjs +36 -5
- package/packages/identity/tests/oauth-credentials.test.mjs +3 -1
- package/packages/identity/tests/portable-identity-architecture.test.mjs +157 -0
- package/packages/identity/tests/session-routes-policy.test.mjs +21 -0
- package/packages/theme-church-site/package.json +2 -1
- package/packages/theme-church-site/src/index.js +1 -0
- package/packages/theme-companions-site/package.json +2 -1
- package/packages/theme-companions-site/src/index.js +1 -0
- package/packages/theme-floors-site/package.json +2 -1
- package/packages/theme-floors-site/src/index.js +1 -0
- package/packages/theme-fuelnfree-site/package.json +2 -1
- package/packages/theme-fuelnfree-site/src/index.js +1 -0
- package/packages/theme-handyman-site/package.json +2 -1
- package/packages/theme-handyman-site/src/index.js +1 -0
- package/packages/theme-insurance-site/package.json +2 -1
- package/packages/theme-insurance-site/src/index.js +1 -0
- package/packages/theme-shinshu-site/package.json +2 -1
- package/packages/theme-shinshu-site/src/index.js +1 -0
- package/protocol/apps/agentsam.app.v1.schema.json +51 -0
- package/protocol/brand/brandpack.v1.schema.json +43 -0
- package/protocol/credentials/issue.v1.schema.json +38 -0
- package/protocol/database/connection.v1.schema.json +41 -0
- package/protocol/embeddings/embedding-profile.v1.schema.json +20 -0
- package/protocol/errors/error-envelope.schema.json +135 -1
- package/protocol/errors/recovery.v1.schema.json +50 -0
- package/protocol/runtime/workspace-fs.v1.schema.json +71 -0
- package/protocol/sam/activity.v1.schema.json +48 -0
- package/protocol/sam/answer.v1.schema.json +35 -0
- package/protocol/sam/calibration.v1.schema.json +21 -0
- package/protocol/sam/decision-receipt.v1.schema.json +29 -0
- package/protocol/sam/evaluation.v1.schema.json +19 -0
- package/protocol/sam/operation.schema.json +66 -0
- package/protocol/sam/outcome.v1.schema.json +36 -0
- package/protocol/sam/question.v1.schema.json +26 -0
- package/protocol/sam/registry.seed.json +153 -0
- package/protocol/sam/result.schema.json +53 -0
- package/protocol/sam/state.v1.schema.json +23 -0
- package/protocol/skills/agentsam.interaction.v1.schema.json +50 -0
- package/protocol/skills/agentsam.skill.v1.schema.json +57 -0
- package/protocol/ui/icon-registry.mjs +226 -0
- package/protocol/ui/icon.v1.schema.json +51 -0
- package/skills/README.md +22 -9
- package/skills/agentsam-codebaseindex/SKILL.md +225 -0
- package/skills/catalog.json +14 -0
- package/src/cli/command-catalog.js +130 -0
- package/src/cli/dispatch.js +48 -0
- package/src/cli.js +43 -1
- package/src/commands/api-key.js +244 -0
- package/src/commands/app.js +60 -28
- package/src/commands/brand.js +17 -19
- package/src/commands/codebaseindex.js +688 -0
- package/src/commands/env.js +152 -25
- package/src/commands/go.js +366 -53
- package/src/commands/interaction-clack.js +115 -0
- package/src/commands/models.js +1 -1
- package/src/commands/providers.js +62 -14
- package/src/commands/shell.js +43 -2
- package/src/commands/skill.js +248 -0
- package/src/commands/skills.js +1 -1
- package/src/commands/start-local.js +4 -0
- package/src/commands/whoami.js +90 -18
- package/src/go/build.js +229 -39
- package/src/go/cloudflare.js +506 -105
- package/src/go/container.js +120 -0
- package/src/go/contract.js +9 -4
- package/src/go/discover.js +149 -33
- package/src/go/index.js +15 -3
- package/src/go/native-probe-runner.mjs +119 -0
- package/src/go/{registry.js → official-registry.js} +64 -7
- package/src/go/official-release.js +10 -0
- package/src/go/receipts.js +77 -10
- package/src/go/verify.js +9 -2
- package/src/index.js +27 -0
- package/src/indexing/ingest/discover-models.js +298 -0
- package/src/indexing/ingest/inventory.js +243 -0
- package/src/indexing/ingest/job-graph.js +181 -0
- package/src/indexing/ingest/materials.js +210 -0
- package/src/lib/provider-credentials.js +63 -21
- package/src/lib/slash-commands.js +1 -0
- package/src/local-fs/capability.js +121 -0
- package/src/local-fs/freshness.js +75 -0
- package/src/local-fs/index.js +385 -0
- package/src/local-fs/paths.js +100 -0
- package/src/local-pty/server.js +295 -19
- package/src/mcp/client.js +2 -2
- package/src/models/ai-access-onboarding.js +112 -0
- package/src/models/discovery.js +10 -2
- package/src/models/inventory-core.js +9 -1
- package/src/sam/activity/index.js +183 -0
- package/src/sam/client.js +252 -0
- package/src/sam/decision/calibration.js +109 -0
- package/src/sam/decision/confidence.js +126 -0
- package/src/sam/decision/evaluate.js +157 -0
- package/src/sam/decision/evaluators/deterministic.js +341 -0
- package/src/sam/decision/evaluators/heuristic.js +61 -0
- package/src/sam/decision/evaluators/select.js +50 -0
- package/src/sam/decision/evaluators/semantic.js +149 -0
- package/src/sam/decision/hierarchical.js +61 -0
- package/src/sam/decision/index.js +53 -0
- package/src/sam/decision/policy.js +86 -0
- package/src/sam/decision/questions.js +120 -0
- package/src/sam/decision/receipt.js +148 -0
- package/src/sam/decision/state.js +117 -0
- package/src/sam/decision/types.js +22 -0
- package/src/sam/decision/validate.js +200 -0
- package/src/sam/define.js +51 -0
- package/src/sam/index.js +65 -0
- package/src/sam/operations/brand-scan.js +62 -0
- package/src/sam/operations/cad-blender-inspect.js +36 -0
- package/src/sam/operations/codebaseindex-ingest.js +49 -0
- package/src/sam/operations/decision-evaluate.js +59 -0
- package/src/sam/operations/planning-astar.js +77 -0
- package/src/sam/operations/planning-goap.js +60 -0
- package/src/sam/operations/repository-inspect.js +72 -0
- package/src/sam/operations/security-scan.js +33 -0
- package/src/sam/operations/terminal-exec.js +29 -0
- package/src/sam/planning/astar.js +311 -0
- package/src/sam/planning/goap.js +177 -0
- package/src/sam/planning/index.js +21 -0
- package/src/sam/planning/state.js +84 -0
- package/src/sam/registry.js +48 -0
- package/src/sam/result.js +77 -0
- package/src/sam/seed.js +44 -0
- package/src/sam/types.js +91 -0
- package/src/skills/catalog.js +64 -0
- package/src/skills/content-resolver.js +124 -0
- package/src/skills/hosted-store.js +37 -0
- package/src/skills/index.js +29 -64
- package/src/skills/interaction.js +102 -0
- package/src/skills/local-store.js +228 -0
- package/src/skills/manifest.js +104 -0
- package/src/skills/metrics.js +31 -0
- package/src/skills/registry.js +184 -0
- package/src/skills/runtime.js +287 -0
- package/src/skills/slash.js +44 -0
- package/src/ui/cli/help.js +94 -101
- package/test/cli/api-key-env-whoami.test.mjs +129 -0
- package/test/cli/codebaseindex-plan-ux.test.mjs +37 -0
- package/test/cli/go.test.mjs +62 -5
- package/test/cli/skill-npm-and-env.test.mjs +52 -0
- package/test/cli/wireframes-go-registry.test.mjs +87 -2
- package/test/go/build-source-identity.test.mjs +31 -0
- package/test/go/cloudflare-probe.test.mjs +274 -10
- package/test/go/distribution.test.mjs +28 -0
- package/test/integration/ai-access-onboarding.test.mjs +47 -0
- package/test/integration/cli-help.test.mjs +1 -1
- package/test/integration/cms-site-tenancy-contract.test.mjs +6 -6
- package/test/integration/codebaseindex-ingest.test.mjs +166 -0
- package/test/integration/icon-registry.test.mjs +67 -0
- package/test/integration/ingest-discover-models.test.mjs +30 -0
- package/test/integration/install-script.test.mjs +12 -9
- package/test/integration/local-fs.test.mjs +113 -0
- package/test/integration/provider-env-cli.test.mjs +2 -1
- package/test/integration/sam-activity-recovery.test.mjs +147 -0
- package/test/integration/sam-decision.test.mjs +584 -0
- package/test/integration/sam-kernel.test.mjs +99 -0
- package/test/integration/sam-planning-astar.test.mjs +279 -0
- package/test/integration/skill-runtime.test.mjs +240 -0
- package/test/integration/studio-fs-pty-e2e.test.mjs +294 -0
- package/test/models.test.mjs +14 -7
- 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
|
+
}
|