sdtk-brain-kit 0.1.0 → 0.1.1

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/README.md CHANGED
@@ -6,7 +6,7 @@ layer (`wiki/`) that you — and your AI agent — can search, lint, and browse
6
6
  a graph. The agent reading the vault (per its `CLAUDE.md` contract) is the
7
7
  knowledge engine; this CLI provides the deterministic rails only.
8
8
 
9
- Package version in this source snapshot: `0.1.0`
9
+ Package version in this source snapshot: `0.1.1`
10
10
  CLI command: `sdtk-brain`
11
11
 
12
12
  Standalone by design: not part of the `sdtk-kit` umbrella, installs no agent
@@ -29,7 +29,7 @@ cp ~/some-article.md raw/inbox/
29
29
  sdtk-brain ingest raw/inbox
30
30
  sdtk-brain compile --mode safe --apply
31
31
  sdtk-brain search "topic"
32
- sdtk-brain open # docs view + graph view (self-contained HTML)
32
+ sdtk-brain open # docs view + graph view (local viewer, no server)
33
33
  ```
34
34
 
35
35
  ## Commands
@@ -1011,10 +1011,8 @@ body.panel-resizing [data-resizable]{transition:none!important;}
1011
1011
  </nav>
1012
1012
 
1013
1013
  <script src="./mermaid.min.js"></script>
1014
+ <script src="./viewer-data.js"></script>
1014
1015
  <script>
1015
- const INDEX = __ATLAS_INDEX_JSON__;
1016
- const GRAPH = __ATLAS_GRAPH_JSON__;
1017
- const FAMILY_COLORS = __ATLAS_FAMILY_COLORS_JSON__;
1018
1016
  const FAMILY_GLYPHS = {
1019
1017
  'governance': 'G', 'product': 'P', 'spec': 'S', 'architecture': 'A', 'database': 'D',
1020
1018
  'api': 'I', 'qa': 'Q', 'design': 'L', 'dev': 'V', 'skill': 'K', 'template': 'T',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sdtk-brain-kit",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Standalone local-first second-brain vault CLI: immutable raw/ sources compiled into a markdown wiki/ knowledge layer, maintained by rails (ingest, compile, lint, search, graph viewer) while your agent is the knowledge engine.",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -24,6 +24,12 @@ Scope:
24
24
  Enriches EXTERNAL tool/repo candidates surfaced by the ingest/compile pipeline.
25
25
  Not related to your own project docs — those are indexed by "sdtk-brain atlas build".
26
26
 
27
+ Options:
28
+ --source <source> Required. Only "github" is supported in R1.
29
+ --mode <mode> Required. Only "review" is supported in R1.
30
+ --project-path <path> Project root. Defaults to the current directory.
31
+ -h, --help Show this help.
32
+
27
33
  Safety:
28
34
  Review mode only in R1.
29
35
  No network fetch, page rewrite, source mutation, apply, entitlement change, or .brain-legacy-unused mutation.
@@ -38,6 +38,10 @@ Scaffolds a second-brain vault:
38
38
  CLAUDE.md the agent operating contract (how the vault is maintained)
39
39
  README.md human orientation
40
40
 
41
+ Options:
42
+ --vault <path> Vault directory (defaults to the current directory).
43
+ -h, --help Show this help.
44
+
41
45
  Safety:
42
46
  Defaults to the current directory as the vault. Refuses a target that is
43
47
  not empty unless it already looks like a vault (re-runs are idempotent and
@@ -1,8 +1,10 @@
1
1
  "use strict";
2
2
 
3
3
  // `sdtk-brain open` — build the vault knowledge graph (shared atlas builder,
4
- // L-5) and open the self-contained viewer. The viewer is a single HTML file
5
- // with the graph JSON inlined, so file:// works without any server.
4
+ // L-5) and open the viewer. The graph directory is self-contained
5
+ // (viewer.html + viewer-data.js + mermaid.min.js, loaded by relative src),
6
+ // so file:// works without any server (BK-321: data is a sibling script,
7
+ // no longer inlined into a 28MB single file).
6
8
 
7
9
  const path = require("path");
8
10
  const { parseFlags } = require("../lib/args");
@@ -23,10 +25,16 @@ Usage:
23
25
  sdtk-brain open [--vault <path>] [--no-open]
24
26
 
25
27
  Builds the vault knowledge graph over wiki/ and raw/ (shared SDTK atlas
26
- builder) into .brain/graph and opens the self-contained viewer.html
27
- (docs view + graph view). --no-open builds without launching a browser.
28
+ builder) into .brain/graph and opens the viewer (docs view + graph view).
29
+ The output directory is self-contained: viewer.html loads its data from
30
+ the sibling viewer-data.js. --no-open builds without launching a browser.
28
31
 
29
- Local-only: no server is required — the viewer inlines its data.`);
32
+ Options:
33
+ --vault <path> Vault directory (defaults to the current directory).
34
+ --no-open Build without launching a browser.
35
+ -h, --help Show this help.
36
+
37
+ Local-only: no server is required — the viewer directory carries its data.`);
30
38
  return 0;
31
39
  }
32
40
 
@@ -46,7 +54,15 @@ async function cmdOpen(args) {
46
54
  return 1;
47
55
  }
48
56
 
49
- const result = await buildAtlas({ projectRoot: vault, outputDir, scanRoots, excludes });
57
+ // BK-326: point the builder's pages/provenance workspace at .brain so no
58
+ // stray .sdtk tree ever lands in the vault root.
59
+ const result = await buildAtlas({
60
+ projectRoot: vault,
61
+ outputDir,
62
+ scanRoots,
63
+ excludes,
64
+ workspaceRoot: path.join(vault, ".brain"),
65
+ });
50
66
  const viewer = path.join(outputDir, "viewer.html");
51
67
  console.log(`[brain] Graph: ${result.node_count} nodes, ${result.edge_count} edges`);
52
68
  console.log(`[brain] Viewer: ${viewer}`);
@@ -110,7 +110,9 @@ Safety:
110
110
  No source mutation, local wiki page writes, graph rebuild, web fetch, Ask, raw/provenance mutation, or .brain-legacy-unused mutation.
111
111
 
112
112
  Next:
113
- sdtk-brain compile --mode safe`);
113
+ sdtk-brain compile --mode safe
114
+
115
+ Pass -h / --help on any verb to show its help.`);
114
116
  return 0;
115
117
  }
116
118
 
@@ -30,6 +30,12 @@ Inputs:
30
30
  Duplicate coverage of the same source is de-duplicated; the atlas version wins.
31
31
  Each match reports its store (atlas | wiki).
32
32
 
33
+ Options:
34
+ --project-path <path> Vault directory (--vault works as an alias).
35
+ --json Emit the full result object as JSON.
36
+ --limit <n> Maximum matches to display (default 10, max 50).
37
+ -h, --help Show this help.
38
+
33
39
  Behavior:
34
40
  Read-only and non-premium.
35
41
  No wiki.ask entitlement is required.
@@ -35,7 +35,7 @@ function countFiles(root, ext) {
35
35
  function cmdStatus(args) {
36
36
  const { flags } = parseFlags(args || [], STATUS_FLAG_DEFS);
37
37
  if (flags.help) {
38
- console.log("Usage: sdtk-brain status [--vault <path>] — read-only vault status.");
38
+ console.log("Usage: sdtk-brain status [--vault <path>] [-h|--help] — read-only vault status.");
39
39
  return 0;
40
40
  }
41
41
  const vault = resolveProjectPath(flags.vault || process.cwd());
@@ -7,7 +7,7 @@ const crypto = require("crypto");
7
7
  // ---------------------------------------------------------------------------
8
8
  // Constants
9
9
  // ---------------------------------------------------------------------------
10
- const ATLAS_STATE_VERSION = 6;
10
+ const ATLAS_STATE_VERSION = 7;
11
11
  const WIKI_PAGE_SCHEMA_VERSION = 1;
12
12
  const WIKI_PROVENANCE_SCHEMA_VERSION = 1;
13
13
 
@@ -514,13 +514,38 @@ function load_atlas_state(atlas_dir) {
514
514
  }
515
515
  }
516
516
 
517
+ // BK-324: the cache exists to skip re-PARSING (reference extraction etc.),
518
+ // not to duplicate content — doc bodies are dropped at save time and
519
+ // rehydrated from the source file on reuse (safe: mtime/hash verified the
520
+ // file is unchanged). Cut the state file ~80% on real corpora.
521
+ function _slim_state_for_save(state) {
522
+ const documents = {};
523
+ for (const [rel, record] of Object.entries(state.documents || {})) {
524
+ if (record && typeof record === "object" && record.doc && typeof record.doc === "object") {
525
+ const doc = { ...record.doc };
526
+ delete doc.body_markdown;
527
+ documents[rel] = { ...record, doc };
528
+ } else {
529
+ documents[rel] = record;
530
+ }
531
+ }
532
+ return { ...state, documents };
533
+ }
534
+
517
535
  function save_atlas_state(state, atlas_dir) {
518
536
  fs.mkdirSync(atlas_dir, { recursive: true });
519
537
  const state_path = _atlas_state_path(atlas_dir);
520
- _write_text_lf(state_path, _escapeNonAscii(JSON.stringify(state, null, 2)));
538
+ _write_text_lf(state_path, _escapeNonAscii(JSON.stringify(_slim_state_for_save(state), null, 2)));
521
539
  return state_path;
522
540
  }
523
541
 
542
+ function _rehydrate_doc_body(doc, md_file) {
543
+ if (doc.body_markdown != null) return doc;
544
+ const text = fs.readFileSync(md_file, { encoding: "utf8" });
545
+ doc.body_markdown = _parse_frontmatter(text)[1];
546
+ return doc;
547
+ }
548
+
524
549
  function build_docs_incremental(root, atlas_dir, generated, scan_roots, exclude_frags) {
525
550
  const prior_state = load_atlas_state(atlas_dir);
526
551
  const prior_documents = prior_state.documents || {};
@@ -545,6 +570,7 @@ function build_docs_incremental(root, atlas_dir, generated, scan_roots, exclude_
545
570
 
546
571
  if (prior_record && typeof prior_record === "object" && prior_doc &&
547
572
  prior_record.mtime === current_mtime) {
573
+ _rehydrate_doc_body(prior_doc, md_file);
548
574
  next_documents[rel] = prior_record;
549
575
  reused_count++;
550
576
  continue;
@@ -553,6 +579,7 @@ function build_docs_incremental(root, atlas_dir, generated, scan_roots, exclude_
553
579
  const current_hash = _compute_file_hash(md_file);
554
580
  if (prior_record && typeof prior_record === "object" && prior_doc &&
555
581
  prior_record.hash === current_hash) {
582
+ _rehydrate_doc_body(prior_doc, md_file);
556
583
  next_documents[rel] = {
557
584
  mtime: current_mtime,
558
585
  hash: current_hash,
@@ -753,24 +780,37 @@ function build_summary(docs, graph, generated, stats, root, scan_roots, exclude_
753
780
  // ---------------------------------------------------------------------------
754
781
  // Viewer builder
755
782
  // ---------------------------------------------------------------------------
756
- function build_viewer(index, graph, generated) {
757
- if (!fs.existsSync(_VIEWER_TEMPLATE_PATH)) {
758
- throw new Error(`Viewer template not found: ${_VIEWER_TEMPLATE_PATH}`);
759
- }
783
+ // BK-321: the doc index/graph are written to a sibling viewer-data.js instead
784
+ // of being inlined into viewer.html (which grew to ~28MB on real corpora).
785
+ // The viewer already loads ./mermaid.min.js by relative src, so a sibling
786
+ // data script works identically under file:// and the atlas server. Top-level
787
+ // const bindings in a classic script are visible to later classic scripts,
788
+ // so the template's main script keeps using INDEX/GRAPH/FAMILY_COLORS as-is.
789
+ function build_viewer_data(index, graph) {
790
+ // String concatenation, not String.replace: replace's string form treats
791
+ // "$&", "$`", "$'", "$$", "$<n>" in doc content as special patterns and
792
+ // spliced garbage into the script (BK-317). Concatenation has no such
793
+ // semantics, so "$"-bearing content is inherently safe here.
760
794
  const index_json = _json_for_inline_script(index);
761
795
  const graph_json = _json_for_inline_script(graph);
762
796
  const family_colors_json = _json_for_inline_script(_FAMILY_COLORS);
797
+ return [
798
+ "// Generated by the SDTK atlas builder — regenerable via `atlas build`, do not edit.",
799
+ "const INDEX = " + index_json + ";",
800
+ "const GRAPH = " + graph_json + ";",
801
+ "const FAMILY_COLORS = " + family_colors_json + ";",
802
+ "",
803
+ ].join("\n");
804
+ }
805
+
806
+ function build_viewer(generated) {
807
+ if (!fs.existsSync(_VIEWER_TEMPLATE_PATH)) {
808
+ throw new Error(`Viewer template not found: ${_VIEWER_TEMPLATE_PATH}`);
809
+ }
763
810
  const template = fs.readFileSync(_VIEWER_TEMPLATE_PATH, "utf8");
764
- // Replacer functions, not plain strings: String.prototype.replace treats a
765
- // string replacement's "$&", "$`", "$'", "$$", "$<n>" sequences as special
766
- // patterns. Indexed doc content routinely contains "$" (shell docs, prices,
767
- // template-literal examples), which previously spliced stray template/JSON
768
- // fragments into the inline script and broke it (BK-317).
769
- return template
770
- .replace(/__ATLAS_GENERATED__/g, () => generated)
771
- .replace(/__ATLAS_INDEX_JSON__/g, () => index_json)
772
- .replace(/__ATLAS_GRAPH_JSON__/g, () => graph_json)
773
- .replace(/__ATLAS_FAMILY_COLORS_JSON__/g, () => family_colors_json);
811
+ // Replacer function per BK-317 discipline (timestamp is "$"-free today, but
812
+ // the string form's special-pattern hazard is not worth re-litigating).
813
+ return template.replace(/__ATLAS_GENERATED__/g, () => generated);
774
814
  }
775
815
 
776
816
  function copy_viewer_assets(atlas_dir) {
@@ -786,9 +826,13 @@ function copy_viewer_assets(atlas_dir) {
786
826
  // ---------------------------------------------------------------------------
787
827
  // Wiki pages + provenance
788
828
  // ---------------------------------------------------------------------------
789
- function _wiki_workspace_root(root) { return path.join(root, ".sdtk", "wiki"); }
790
- function _wiki_pages_root(root) { return path.join(_wiki_workspace_root(root), "pages"); }
791
- function _wiki_provenance_root(root) { return path.join(_wiki_workspace_root(root), "provenance"); }
829
+ // BK-326: the workspace root is overridable so hosts with a different
830
+ // machine-state home (sdtk-brain uses `.brain/`) don't get a stray
831
+ // `.sdtk/wiki` tree leaked into their vault root. sdtk-wiki's default is
832
+ // unchanged.
833
+ function _wiki_workspace_root(root, workspace_root) {
834
+ return workspace_root ? path.resolve(workspace_root) : path.join(root, ".sdtk", "wiki");
835
+ }
792
836
 
793
837
  function _stable_page_id(source_path) {
794
838
  const norm = source_path.replace(/\\/g, "/");
@@ -803,12 +847,12 @@ function _safe_slug(value) {
803
847
  return slug || "page";
804
848
  }
805
849
 
806
- function _page_relative_path(doc) {
850
+ function _page_relative_path(doc, pages_prefix) {
807
851
  const source_path = String(doc.path).replace(/\\/g, "/");
808
852
  const source_digest = crypto.createHash("sha256").update(source_path, "utf8").digest("hex").slice(0, 8);
809
853
  const slug = _safe_slug(String(doc.title || path.parse(source_path).name));
810
854
  const family = _safe_slug(String(doc.family || "other-markdown"));
811
- return `.sdtk/wiki/pages/${family}/${slug}--${source_digest}.md`;
855
+ return `${pages_prefix || ".sdtk/wiki/pages"}/${family}/${slug}--${source_digest}.md`;
812
856
  }
813
857
 
814
858
  function _yaml_quote(value) {
@@ -871,10 +915,16 @@ function _build_change_set(prior_hashes, current_hashes) {
871
915
  return { added, changed, unchanged, removed };
872
916
  }
873
917
 
874
- function write_wiki_pages_and_provenance(docs, state, root, generated, scan_roots) {
875
- const workspace_root = _wiki_workspace_root(root);
876
- const pages_root = _wiki_pages_root(root);
877
- const provenance_root = _wiki_provenance_root(root);
918
+ function write_wiki_pages_and_provenance(docs, state, root, generated, scan_roots, workspaceRoot) {
919
+ const workspace_root = _wiki_workspace_root(root, workspaceRoot);
920
+ const pages_root = path.join(workspace_root, "pages");
921
+ const provenance_root = path.join(workspace_root, "provenance");
922
+ // Page paths are recorded relative to the project root (posix separators)
923
+ // so provenance and _index.md stay portable.
924
+ const pages_prefix = path
925
+ .relative(root, pages_root)
926
+ .split(path.sep)
927
+ .join("/");
878
928
  const sources_path = path.join(provenance_root, "sources.json");
879
929
  const changes_path = path.join(provenance_root, "changes.json");
880
930
 
@@ -897,7 +947,7 @@ function write_wiki_pages_and_provenance(docs, state, root, generated, scan_root
897
947
  source_hash = crypto.createHash("sha256").update(source_path, "utf8").digest("hex");
898
948
  }
899
949
  const page_id = _stable_page_id(source_path);
900
- const page_rel = _page_relative_path(doc);
950
+ const page_rel = _page_relative_path(doc, pages_prefix);
901
951
  const page_path = path.join(root, page_rel);
902
952
 
903
953
  _assert_inside(workspace_root, page_path);
@@ -971,7 +1021,7 @@ function write_wiki_pages_and_provenance(docs, state, root, generated, scan_root
971
1021
  // ---------------------------------------------------------------------------
972
1022
  // Top-level buildAtlas
973
1023
  // ---------------------------------------------------------------------------
974
- async function buildAtlas({ projectRoot, outputDir, scanRoots, excludes, verbose } = {}) {
1024
+ async function buildAtlas({ projectRoot, outputDir, scanRoots, excludes, verbose, workspaceRoot } = {}) {
975
1025
  const generated = _now_utc();
976
1026
  const root = path.resolve(projectRoot);
977
1027
  const atlas_dir = path.resolve(outputDir);
@@ -1008,7 +1058,7 @@ async function buildAtlas({ projectRoot, outputDir, scanRoots, excludes, verbose
1008
1058
  process.stdout.write(`[atlas] Graph: ${graph.nodes.length} nodes, ${graph.edges.length} edges.\n`);
1009
1059
 
1010
1060
  process.stdout.write("[atlas] Writing wiki pages and provenance...\n");
1011
- const wiki_result = write_wiki_pages_and_provenance(docs, state, root, generated, roots);
1061
+ const wiki_result = write_wiki_pages_and_provenance(docs, state, root, generated, roots, workspaceRoot);
1012
1062
  process.stdout.write(`[atlas] Wiki pages: ${wiki_result.page_count}\n`);
1013
1063
 
1014
1064
  const index_data = {
@@ -1036,7 +1086,9 @@ async function buildAtlas({ projectRoot, outputDir, scanRoots, excludes, verbose
1036
1086
  const summary_path = path.join(atlas_dir, "SDTK_DOC_ATLAS_SUMMARY.md");
1037
1087
  _write_text_lf(summary_path, summary_text);
1038
1088
 
1039
- const viewer_html = build_viewer(index_data, graph_out, generated);
1089
+ const viewer_data_js = build_viewer_data(index_data, graph_out);
1090
+ _write_text_lf(path.join(atlas_dir, "viewer-data.js"), viewer_data_js);
1091
+ const viewer_html = build_viewer(generated);
1040
1092
  const viewer_path = path.join(atlas_dir, "viewer.html");
1041
1093
  _write_text_lf(viewer_path, viewer_html);
1042
1094
 
@@ -1089,6 +1141,7 @@ module.exports = {
1089
1141
  build_graph,
1090
1142
  build_summary,
1091
1143
  build_viewer,
1144
+ build_viewer_data,
1092
1145
  write_wiki_pages_and_provenance,
1093
1146
  load_atlas_state,
1094
1147
  save_atlas_state,