{"version":3,"file":"init-BWFFHDtL.mjs","names":[],"sources":["../src/workspace/init.ts"],"sourcesContent":["import { access, mkdir, writeFile } from 'node:fs/promises'\nimport path from 'node:path'\nimport { LOCAL_GRAPH_MCP_ENDPOINT } from '../config/mcp-endpoint.js'\n\nexport interface InitWorkspaceOptions {\n  targetDir: string\n  force?: boolean\n}\n\nexport interface InitWorkspaceResult {\n  workspaceRoot: string\n  filesWritten: string[]\n}\n\nconst WORKSPACE_DIRS = [\n  '.chain-insights',\n  '.chain-insights/schema',\n  '.chain-insights/runtime',\n  '.chain-insights/runtime/logs',\n  '.chain-insights/runtime-skill',\n  'artifacts',\n  'entities',\n  'imports',\n  'reports',\n  'reports/graphs',\n  'reports/tables',\n  'sessions',\n  'templates',\n  'published',\n]\n\nconst DEFAULT_DOMAIN_HINTS = ['aml']\n\nfunction todayIso(): string {\n  return new Date().toISOString().slice(0, 10)\n}\n\nfunction workspaceJson(workspaceRoot: string): string {\n  return JSON.stringify({\n    schema: 'chain-insights.workspace.v1',\n    name: 'Chain Insights Workspace',\n    workspace_root: workspaceRoot,\n    default_network: 'bittensor',\n    graph_mcp_endpoint: LOCAL_GRAPH_MCP_ENDPOINT,\n    artifacts_dir: 'artifacts',\n    imports_dir: 'imports',\n    reports_dir: 'reports',\n    templates_dir: 'templates',\n    domain_hints: DEFAULT_DOMAIN_HINTS,\n    created_at: todayIso(),\n  }, null, 2) + '\\n'\n}\n\nconst README = `# Chain Insights Workspace\n\nThis is a generic Chain Insights workspace.\nUse any editor or agent tooling you want to review reports, artifacts, entity\nnotes, graphs, and published bundles alongside the Chain Insights runtime\nmetadata.\n\n## Start\n\n\\`\\`\\`bash\nchain-insights mcp tools --refresh\nchain-insights wallet ready --check-only\n\\`\\`\\`\n\n## Layout\n\n\\`\\`\\`text\n.chain-insights/   Workspace metadata\nartifacts/         Tool-generated artifacts and durable workspace records\nentities/          Entity notes and indexes\nimports/           External reports, CSVs, screenshots, raw notes\nreports/           Final or interim analyst reports\nreports/graphs/    Graph JSON for visualization\nreports/tables/    Compact tabular extracts\nsessions/          Optional session notes\ntemplates/         Reusable workspace templates\npublished/         Published bundles and handoff-ready exports\n.chain-insights/schema/         Runtime graph schema captures\n.chain-insights/runtime/        Workspace-local runtime process state and debug logs\n.chain-insights/runtime-skill/  Workspace-specific agent schema notes\n\\`\\`\\`\n`\n\nconst AGENTS = `# Agent Instructions\n\nYou are operating inside a Chain Insights workspace.\n\n- Read README.md first.\n- If this directory is not initialized, run \\`cia init .\\` before persistence-producing commands.\n- Do not rerun init in an existing workspace unless replacing scaffolding with \\`--force\\`.\n- Read .chain-insights/runtime-skill/SKILL.md before graph queries.\n- Preserve full blockchain addresses exactly.\n- Do not guess the network for graph queries.\n- Capture or refresh graph schema before the first graph workflow.\n- \\`domain_hints\\` in \\`.chain-insights/workspace.json\\` are optional advisory workflow\n  preferences (for example \\`[\"aml\"]\\`) and should guide, not constrain,\n  tool selection.\n- Save compact artifacts with original graph field names.\n- Put canonical graph JSON in reports/graphs/ and analyst tables in reports/tables/.\n- Markdown reports should summarize and point to graph/table outputs; do not paste large raw JSON blobs into reports.\n- Workspace output must stay in this initialized workspace.\n- Never write artifacts, reports, graph JSON, HTML, schema captures, or logs to ~/.chain-insights.\n- Keep theories lightweight until evidence supports them.\n`\n\nconst CLAUDE = AGENTS\n\nconst CASE_BRIEF = `# Workspace Brief\n\n## Summary\n\nStatus:\nNetwork:\nCurrent Assessment:\n\n## Known Addresses\n\n## Claims To Validate\n\n## Evidence\n\n## Next Steps\n`\n\nconst IMPORTS_README = `# External Investigation Inputs\n\nPut user-provided or third-party material here before turning it into durable\nworkspace outputs.\n\nExamples:\n\n- Exchange support exports\n- CSV extracts\n- Screenshots\n- Raw notes\n- Partner reports\n\nFiles in this directory are inputs, not verified evidence. When an import\nsupports a claim, summarize it into a workspace report or artifact note and\nreference the original file path.\n`\n\nconst TEMPLATES_README = `# Reusable Workspace Templates\n\nStore local report, prompt, artifact, and note templates here.\n\nTemplates are optional workspace helpers. They are not evidence and should not\nbe treated as durable workspace state until copied into an artifact, report,\nentity note, or session note.\n`\n\nconst RUNTIME_SKILL = `---\nname: chain-insights-runtime-schema\ndescription: Workspace-local Chain Insights runtime schema notes. Refresh this after connecting to a graph MCP endpoint.\n---\n\n# Runtime Graph Schema\n\nBefore the first investigation query, capture the live graph schema into:\n\n\\`\\`\\`text\n.chain-insights/schema/<network>.graph-schema.json\n\\`\\`\\`\n\nUse \\`graph_query_batch\\` for schema capture. Prefix current topology reads\nwith \\`USE live_topology\\`, historical topology reads with\n\\`USE archive_topology\\`, and fact reads with \\`USE facts\\`, for example:\n\n\\`\\`\\`bash\ncia mcp call graph_query_batch network=<network> 'queries=[{\"id\":\"node_labels\",\"query\":\"USE live_topology MATCH (n:Identity) RETURN \\\"Identity\\\" AS node_label, count(n) AS sample_count LIMIT 1\"},{\"id\":\"archive_flow_sample\",\"query\":\"USE archive_topology MATCH (:Identity)-[f:FLOWS_TO]->(:Identity) RETURN f.period_granularity AS granularity, f.amount_usd_sum AS amount_usd_sum LIMIT 20\"},{\"id\":\"archive_member_address_sample\",\"query\":\"USE archive_topology MATCH (i:Identity)-[:HAS_ADDRESS]->(m:Address) RETURN i.identity_id AS identity_id, m.address AS member_address, m.network AS member_network LIMIT 20\"}]'\n\\`\\`\\`\n\nThen update this file with observed labels, relationship types, and allowed\nproperty names for the active network.\n\nThe slim identity graph schema:\n\n- Every topology node is \\`(:Identity)\\`, keyed by \\`identity_id\\` in the\n  canonical prefixed form \\`<network>:<canonical_address>\\` (for example\n  \\`bittensor:0x1874a43d7c6d888f9eda3d22a3a49704e3cadb24\\`). Satellite\n  \\`(:Address)\\` nodes exist only for member-address lookup. There is no\n  \\`network\\` property; each network has its own graph.\n- Identity nodes carry no member-address list property. Member-address\n  forms (the canonical 0x form, plus the SS58 substrate form when the\n  identity has a substrate source) live exclusively on the satellite\n  \\`(:Address {address, network})\\` nodes reached via\n  \\`(:Identity)-[:HAS_ADDRESS]->(:Address)\\`. Enumerate an identity's\n  member forms with:\n  \\`MATCH (i:Identity {identity_id: $id})-[:HAS_ADDRESS]->(m:Address)\n  RETURN m.address, m.network\\`. Use those satellite forms to report the\n  human-recognizable address forms; never invent address conversions.\n- Resolve any member address form (0x or SS58) to its identity through\n  the indexed exact lookup:\n  \\`MATCH (m:Address {address: $input})<-[:HAS_ADDRESS]-(i:Identity)\n  RETURN i.identity_id LIMIT 1\\`. \\`:Address(address)\\` is unique and\n  index-backed.\n- Other Identity properties: \\`labels\\` (array) and \\`is_exchange\\`\n  (sparse true/null traversal hint).\n- Identity nodes carry a slim live risk verdict for quick triage\n  (\\`risk_score\\` float, \\`risk_level\\` string) plus base activity rollups:\n  \\`degree_in\\`/\\`degree_out\\`/\\`degree_total\\` (distinct counterparty\n  identities), \\`tx_in_count\\`/\\`tx_out_count\\`/\\`tx_total_count\\`,\n  \\`total_in_usd\\`/\\`total_out_usd\\`/\\`total_volume_usd\\`, \\`net_flow_usd\\`\n  (in minus out; positive = net receiver) — all computed from external\n  flows only — and \\`first_activity_timestamp\\`/\n  \\`last_activity_timestamp\\`/\\`activity_span_days\\`, which include all\n  flows (self-loops included). Movement between an identity's own member\n  forms is excluded from the degree/count/USD rollups and exposed\n  separately as\n  \\`internal_tx_count\\`/\\`internal_volume_usd\\` (sparse: absent when zero,\n  like \\`is_exchange\\`). FLOWS_TO edges carry \\`tx_count\\`,\n  \\`amount_usd_sum\\`, \\`avg_tx_size_usd\\` (understates when\n  \\`price_coverage_ratio\\` < 1), \\`first_seen_timestamp\\`/\n  \\`last_seen_timestamp\\`, \\`first_tx_id\\`/\\`last_tx_id\\`,\n  \\`dominant_asset\\` (largest USD share), and \\`price_coverage_ratio\\`.\n  Lifetime aggregates are the only serving window.\n- Money flow is \\`(:Identity)-[:FLOWS_TO]->(:Identity)\\`. Public AML tools\n  accept blockchain/member addresses and resolve them to identity-grain\n  topology internally.\n- Detailed, provenanced scoring comes from \\`USE facts\\`. ML risk with\n  model versions and processing dates:\n  \\`(:Identity)-[:HAS_RISK_SCORE]->(:RiskScore)\\`; label risk:\n  \\`(:Identity)-[:HAS_LABEL]->(:AddressLabel)\\`; lifetime metrics:\n  \\`(:Identity)-[:HAS_FEATURE]->(:AddressFeature)\\`. Facts identity keys\n  match live \\`identity_id\\` values exactly. Node \\`risk_score\\`/\n  \\`risk_level\\` are quick-triage verdicts only; do not read \\`ml_*\\`,\n  \\`confluence_score\\`, or \\`pattern_flags\\` off topology nodes — those\n  properties do not exist.\n\nRules:\n\n- Prefer \\`graph_query\\` and \\`graph_query_batch\\` for graph-language reads.\n- Do not use legacy \\`topology_scope\\` arguments to choose a graph. If an\n  older endpoint surfaces that argument, treat it as compatibility routing\n  only. The live/archive/facts graph choice stays inside the query via\n  \\`USE ...\\`.\n- Use \\`USE live_topology\\` for recent topology, \\`USE archive_topology\\`\n  for historical topology, and \\`USE facts\\` for labels, features,\n  risk scores, assets, and enrichment. Archived money-flow topology is\n  exposed as \\`(:Identity)-[:FLOWS_TO]->(:Identity)\\` with\n  \\`period_granularity\\`, \\`period_start_date\\`, and \\`period_end_date\\` on\n  the relationship. Archive member-address lookup is exposed as\n  \\`(:Identity)-[:HAS_ADDRESS]->(:Address)\\` with \\`Address.address\\` and\n  member-ledger \\`Address.network\\` projected for member-address resolution.\n- Preserve source schema field names in generated data files.\n- Do not rename, reinterpret, or add unit labels to graph fields unless the\n  schema or query result explicitly supports that interpretation.\n- Keep persisted outputs compact: select only the fields needed to support the claim.\n  Avoid storing whole node or relationship property blobs unless\n  the purpose of the query is schema discovery or debugging.\n- When using BFS, fixed-depth traversal fallbacks, or any manual \\`FLOWS_TO\\`\n  traversal, treat exchange hot wallets as terminal endpoints only. Do not\n  expand from, through, or classify exchange nodes as deposit, suspect, or\n  intermediate candidates. In Cypher, require every non-terminal traversal node\n  to satisfy \\`is_exchange IS NULL\\`; only the final exchange endpoint should\n  satisfy \\`is_exchange IS NOT NULL\\`.\n- Keep analysis products separate from summary notes: graph JSON belongs under\n  \\`reports/graphs/\\`, tabular extracts under \\`reports/tables/\\`, and analyst\n  narrative under \\`reports/\\`.\n- Markdown reports should be short provenance records with key facts and\n  pointers. Large JSON belongs in \\`reports/tables/\\`, not inline in reports.\n\nTrace tool chaining:\n\n1. Use \\`aml_trace_victim_funds\\` when the user gives victim/source addresses.\n2. Pass returned \\`continuation.candidate_deposit_addresses\\` to\n   \\`aml_trace_deposit_sources\\`; do not make victim tracing run deposit traceback\n   internally.\n3. Pass high-confidence \\`continuation.candidate_suspect_addresses\\` from\n   deposit traceback to \\`aml_trace_suspect_funds\\`.\n4. Use \\`aml_trace_suspect_funds\\` when the user gives suspected scammer, mule,\n   operator, or laundering-ring addresses. \\`incident_timestamp_ms\\` is\n   optional.\n5. Use \\`aml_address_risk\\` for single-address enrichment, and\n   \\`graph_query_batch\\` only when the role-specific tools do not answer the\n   exact question.\n\nAll trace tools take blockchain/member addresses as inputs, resolve them to\ncanonical identity keys internally, and return \\`chain-insights.trace.v1\\`.\nPreserve full blockchain addresses in\n\\`input.addresses\\`, \\`addresses[].address\\`, \\`edges[].from_address\\`,\n\\`edges[].to_address\\`, \\`paths[].addresses\\`, \\`candidate_labels[].address\\`,\nand \\`continuation\\` address lists. Preserve canonical identity mappings only\ninside explicit \\`identity_resolution\\` audit metadata.\n`\n\nconst SCHEMA_README = `# Runtime Schema Captures\n\nStore graph schema captures here, for example:\n\n\\`\\`\\`text\nbittensor.graph-schema.json\n\\`\\`\\`\n\nSchema captures should be generated before the first graph workflow in a fresh\nworkspace, then referenced by artifacts, reports, and runtime skill notes.\n`\n\nfunction workspaceFiles(workspaceRoot: string): Array<[string, string]> {\n  return [\n    ['.chain-insights/workspace.json', workspaceJson(workspaceRoot)],\n    ['README.md', README],\n    ['AGENTS.md', AGENTS],\n    ['CLAUDE.md', CLAUDE],\n    ['imports/README.md', IMPORTS_README],\n    ['templates/README.md', TEMPLATES_README],\n    ['templates/workspace-brief.md', CASE_BRIEF],\n    ['.chain-insights/runtime-skill/SKILL.md', RUNTIME_SKILL],\n    ['.chain-insights/schema/README.md', SCHEMA_README],\n    ['.chain-insights/runtime/.keep', ''],\n    ['.chain-insights/runtime/logs/.keep', ''],\n  ]\n}\n\nasync function assertNoFileCollisions(workspaceRoot: string): Promise<void> {\n  for (const [relativePath] of workspaceFiles(workspaceRoot)) {\n    const filePath = path.join(workspaceRoot, relativePath)\n    try {\n      await access(filePath)\n      throw new Error(`Refusing to overwrite ${filePath}. Re-run with --force to replace workspace files.`)\n    } catch (err) {\n      if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n        continue\n      }\n      throw err\n    }\n  }\n}\n\nexport async function initWorkspace(options: InitWorkspaceOptions): Promise<InitWorkspaceResult> {\n  const workspaceRoot = path.resolve(options.targetDir)\n  if (!options.force) {\n    await assertNoFileCollisions(workspaceRoot)\n  }\n\n  for (const dir of WORKSPACE_DIRS) {\n    await mkdir(path.join(workspaceRoot, dir), { recursive: true })\n  }\n\n  const filesWritten: string[] = []\n  const flag = options.force ? 'w' : 'wx'\n  for (const [relativePath, content] of workspaceFiles(workspaceRoot)) {\n    const filePath = path.join(workspaceRoot, relativePath)\n    try {\n      await writeFile(filePath, content, { mode: 0o600, flag })\n      filesWritten.push(relativePath)\n    } catch (err) {\n      if ((err as NodeJS.ErrnoException).code === 'EEXIST') {\n        throw new Error(`Refusing to overwrite ${filePath}. Re-run with --force to replace workspace files.`)\n      }\n      throw err\n    }\n  }\n\n  return { workspaceRoot, filesWritten }\n}\n"],"mappings":";;;;AAcA,MAAM,iBAAiB;CACrB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,MAAM,uBAAuB,CAAC,KAAK;AAEnC,SAAS,WAAmB;CAC1B,wBAAO,IAAI,KAAK,EAAA,CAAE,YAAY,CAAC,CAAC,MAAM,GAAG,EAAE;AAC7C;AAEA,SAAS,cAAc,eAA+B;CACpD,OAAO,KAAK,UAAU;EACpB,QAAQ;EACR,MAAM;EACN,gBAAgB;EAChB,iBAAiB;EACjB,oBAAoB;EACpB,eAAe;EACf,aAAa;EACb,aAAa;EACb,eAAe;EACf,cAAc;EACd,YAAY,SAAS;CACvB,GAAG,MAAM,CAAC,IAAI;AAChB;AAEA,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCf,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;AAsBf,MAAM,SAAS;AAEf,MAAM,aAAa;;;;;;;;;;;;;;;;AAiBnB,MAAM,iBAAiB;;;;;;;;;;;;;;;;;AAkBvB,MAAM,mBAAmB;;;;;;;;AASzB,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuItB,MAAM,gBAAgB;;;;;;;;;;;AAYtB,SAAS,eAAe,eAAgD;CACtE,OAAO;EACL,CAAC,kCAAkC,cAAc,aAAa,CAAC;EAC/D,CAAC,aAAa,MAAM;EACpB,CAAC,aAAa,MAAM;EACpB,CAAC,aAAa,MAAM;EACpB,CAAC,qBAAqB,cAAc;EACpC,CAAC,uBAAuB,gBAAgB;EACxC,CAAC,gCAAgC,UAAU;EAC3C,CAAC,0CAA0C,aAAa;EACxD,CAAC,oCAAoC,aAAa;EAClD,CAAC,iCAAiC,EAAE;EACpC,CAAC,sCAAsC,EAAE;CAC3C;AACF;AAEA,eAAe,uBAAuB,eAAsC;CAC1E,KAAK,MAAM,CAAC,iBAAiB,eAAe,aAAa,GAAG;EAC1D,MAAM,WAAW,KAAK,KAAK,eAAe,YAAY;EACtD,IAAI;GACF,MAAM,OAAO,QAAQ;GACrB,MAAM,IAAI,MAAM,yBAAyB,SAAS,kDAAkD;EACtG,SAAS,KAAK;GACZ,IAAK,IAA8B,SAAS,UAC1C;GAEF,MAAM;EACR;CACF;AACF;AAEA,eAAsB,cAAc,SAA6D;CAC/F,MAAM,gBAAgB,KAAK,QAAQ,QAAQ,SAAS;CACpD,IAAI,CAAC,QAAQ,OACX,MAAM,uBAAuB,aAAa;CAG5C,KAAK,MAAM,OAAO,gBAChB,MAAM,MAAM,KAAK,KAAK,eAAe,GAAG,GAAG,EAAE,WAAW,KAAK,CAAC;CAGhE,MAAM,eAAyB,CAAC;CAChC,MAAM,OAAO,QAAQ,QAAQ,MAAM;CACnC,KAAK,MAAM,CAAC,cAAc,YAAY,eAAe,aAAa,GAAG;EACnE,MAAM,WAAW,KAAK,KAAK,eAAe,YAAY;EACtD,IAAI;GACF,MAAM,UAAU,UAAU,SAAS;IAAE,MAAM;IAAO;GAAK,CAAC;GACxD,aAAa,KAAK,YAAY;EAChC,SAAS,KAAK;GACZ,IAAK,IAA8B,SAAS,UAC1C,MAAM,IAAI,MAAM,yBAAyB,SAAS,kDAAkD;GAEtG,MAAM;EACR;CACF;CAEA,OAAO;EAAE;EAAe;CAAa;AACvC"}