@chrisdudek/yg 2.0.0 → 2.2.0

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
@@ -41,7 +41,7 @@ yg build-context --node orders/order-service
41
41
 
42
42
  **Diagnostics:**
43
43
 
44
- - `yg preflight [--quick]` — Unified diagnostic report: journal, drift, status, validation
44
+ - `yg preflight [--quick]` — Unified diagnostic report: drift, status, validation
45
45
 
46
46
  **Reading and analysis:**
47
47
 
@@ -61,12 +61,6 @@ yg build-context --node orders/order-service
61
61
  - `yg drift-sync --node <path> [--recursive]` — Record current file hash after resolving drift
62
62
  - `yg drift-sync --all` — Sync all nodes with mappings in one command
63
63
 
64
- **Session journal:**
65
-
66
- - `yg journal-add --note "..." [--target <node-path>]` — Buffer a decision note
67
- - `yg journal-read` — List pending journal entries
68
- - `yg journal-archive` — Archive journal after consolidation
69
-
70
64
  **Setup:**
71
65
 
72
66
  - `yg init --platform <name>` — Initialize `.yggdrasil/` structure (once per repository)
package/dist/bin.js CHANGED
@@ -54,14 +54,30 @@ import path from "path";
54
54
  // src/templates/rules.ts
55
55
  var CORE_PROTOCOL = `## CORE PROTOCOL
56
56
 
57
- Yggdrasil is persistent semantic memory stored in \`.yggdrasil/\`. It maps the repository and provides deterministic implementation context for every node. This document is your complete operating manual. Follow it strictly.
57
+ <EXTREMELY-IMPORTANT>
58
+ This is your operating manual for working in a Yggdrasil-managed repository. Every rule below is mandatory \u2014 no skill, plan, workflow, or instruction overrides these requirements.
59
+
60
+ BEFORE working with ANY source file, you MUST run \`yg owner\` to check if it is mapped. If mapped, you MUST use the graph before reading source. YOU DO NOT HAVE A CHOICE.
61
+
62
+ This is not negotiable. This is not optional. You cannot rationalize your way out of this.
63
+ </EXTREMELY-IMPORTANT>
64
+
65
+ Yggdrasil is persistent semantic memory stored in \`.yggdrasil/\`. It maps the repository and provides deterministic implementation context for every node.
58
66
 
59
67
  ### Quick Start Protocol
60
68
 
61
69
  \`\`\`
62
70
  BEFORE reading, researching, planning, OR modifying ANY mapped file:
71
+ 0. Don't know which file or node to start from? If a semantic search
72
+ tool is available, search for your intent \u2014 the graph contains
73
+ responsibility, flow, and aspect files with rich natural-language
74
+ descriptions that match goal-oriented queries. Use the results
75
+ to identify relevant nodes, then proceed to step 1.
63
76
  1. yg owner --file <path>
64
- 2. yg build-context --node <owner>
77
+ 2. Choose the right graph tool for your task:
78
+ - Understanding how/why it works \u2192 yg build-context --node <owner>
79
+ - Assessing what is affected by a change \u2192 yg impact --node <owner>
80
+ - Planning modifications \u2192 both (build-context first, then impact)
65
81
  The context package is your primary source of ARCHITECTURAL understanding:
66
82
  intent, constraints, relations, rationale. For IMPLEMENTATION precision
67
83
  (exact behavior, error handling, await patterns, edge cases) \u2014 verify
@@ -81,6 +97,8 @@ NEVER: modify code without graph coverage.
81
97
  NEVER: read mapped source files to understand a component without
82
98
  running yg build-context first \u2014 the graph captures intent,
83
99
  constraints, and relations that source files cannot.
100
+ NEVER: assess blast radius of a change without running yg impact first
101
+ \u2014 the graph knows the dependency structure that grep cannot infer.
84
102
  NEVER: invent rationale, business rules, or decisions.
85
103
  NEVER: auto-resolve drift without asking the user.
86
104
  WHEN UNSURE: ask the user. Never guess. Never assume.
@@ -88,7 +106,7 @@ WHEN UNSURE: ask the user. Never guess. Never assume.
88
106
 
89
107
  ### Five Core Rules
90
108
 
91
- 1. **Graph first.** Before reading, researching, planning, or modifying mapped files, run \`yg owner\` and \`yg build-context\`. Always. The context package is your primary source of architectural understanding. For implementation-level precision (exact behavior, error paths, edge cases) \u2014 verify against source code after loading the context package.
109
+ 1. **Graph first.** Before reading, researching, planning, or modifying mapped files, run \`yg owner\` and the appropriate graph tool: \`yg build-context\` to understand a component, \`yg impact\` to assess blast radius. The graph is your primary source of architectural understanding. For implementation-level precision (exact behavior, error paths, edge cases) \u2014 verify against source code after loading the context package.
92
110
  2. **Code and graph are one.** Code changed \u2192 graph updated in the same response. Graph changed \u2192 source verified in the same response. No exceptions.
93
111
  3. **Never invent why.** The graph captures human intent. If you don't know why something was decided, ask. Never hallucinate rationale.
94
112
  4. **Always capture why \u2014 especially why NOT.** When the user explains a reason, record it in the graph immediately. When a design choice is made, also record rejected alternatives: "Chose X over Y because Z." Rejected alternatives are the highest-value information \u2014 invisible in code and irrecoverable once forgotten. Conversation evaporates; graph persists.
@@ -96,15 +114,22 @@ WHEN UNSURE: ask the user. Never guess. Never assume.
96
114
 
97
115
  ### Recognizing Graph-Required Actions
98
116
 
99
- What matters is the ACTION you are performing, not what instructed it. If the action involves understanding mapped code, the graph protocol applies \u2014 whether the instruction came from a skill, a plan, a user message, a workflow step, or your own initiative.
117
+ What matters is the ACTION you are performing, not what instructed it. If the action involves reading, understanding, or modifying mapped code, the graph protocol applies \u2014 whether the instruction came from a skill, a plan, a user message, a brainstorming session, a debugging workflow, or your own initiative. This is not negotiable. You cannot rationalize your way out of this.
100
118
 
101
- **Actions that require \`yg owner\` + \`yg build-context\` first:**
119
+ **Actions that require \`yg owner\` + \`yg build-context\`:**
102
120
 
103
121
  - Reading or exploring source files to understand a component
104
122
  - Proposing approaches, designs, or plans for changing code
105
123
  - Reviewing or debugging code
106
124
  - Any form of reasoning about how mapped code works or should change
107
125
 
126
+ **Actions that require \`yg owner\` + \`yg impact\`:**
127
+
128
+ - Assessing blast radius before changing or removing a component
129
+ - Finding all dependents of a component
130
+ - Planning cross-cutting refactors or feature removals
131
+ - Scoping work that spans multiple nodes
132
+
108
133
  **Actions that do NOT require yg:**
109
134
 
110
135
  - Git operations (log, diff, status, blame)
@@ -112,11 +137,23 @@ What matters is the ACTION you are performing, not what instructed it. If the ac
112
137
  - Running tests, builds, or linters
113
138
  - Working with files that \`yg owner\` reports as unmapped
114
139
 
140
+ **Evasion patterns \u2014 if you think any of these, STOP:**
141
+
142
+ | Thought | Reality |
143
+ |---|---|
144
+ | "The skill/plan says to explore the codebase" | Exploring mapped code = yg owner + graph tool first |
145
+ | "I'm just scoping/searching, not understanding" | Scoping IS a graph action; use yg impact |
146
+ | "The plan step says to read this file" | Reading a mapped file = yg owner first |
147
+ | "I'm brainstorming, not implementing" | Brainstorming about mapped code needs graph context |
148
+ | "I'm only grepping for references" | Grep finds text; yg impact finds structural dependencies. Use both. |
149
+ | "I'll use the graph later when I modify" | Graph-first means BEFORE reading, not before modifying |
150
+ | "I'll grep the codebase to find where to start" | If semantic search is available, search by intent first \u2014 graph files are designed to match natural-language goals. Then \`yg owner\` on results. |
151
+
115
152
  ### Failure States
116
153
 
117
154
  You have broken Yggdrasil if you do any of the following:
118
155
 
119
- - \u274C Worked on a mapped file without running \`yg owner\` + \`yg build-context\` first \u2014 regardless of what instructed the action (skill, plan, user request, workflow step).
156
+ - \u274C Worked on a mapped file without running \`yg owner\` + the appropriate graph tool (\`build-context\` or \`impact\`) first \u2014 regardless of what instructed the action (skill, plan, user request, workflow step).
120
157
  - \u274C Modified source code without updating graph artifacts in the same response, or vice versa.
121
158
  - \u274C Resolved a code-graph inconsistency or ambiguity without asking the user first.
122
159
  - \u274C Created or edited a graph element without reading its schema in \`schemas/\` first.
@@ -161,9 +198,8 @@ var OPERATIONS = `## OPERATIONS
161
198
  \`\`\`
162
199
  PREFLIGHT (every conversation, before any work):
163
200
  - [ ] 1. yg preflight \u2192 read unified report
164
- - [ ] 2. If journal entries: consolidate to graph, then yg journal-archive
165
- - [ ] 3. If drift: resolve per Drift Resolution, then yg drift-sync per node
166
- - [ ] 4. If validation errors: fix, re-run yg validate
201
+ - [ ] 2. If drift: resolve per Drift Resolution, then yg drift-sync per node
202
+ - [ ] 3. If validation errors: fix, re-run yg validate
167
203
  Exception: read-only requests (explain, analyze) \u2014 skip preflight.
168
204
 
169
205
  UNDERSTANDING mapped code (questions, research, OR planning):
@@ -174,14 +210,14 @@ UNDERSTANDING mapped code (questions, research, OR planning):
174
210
  Raw reads supplement the context package \u2014 they do not replace it.
175
211
 
176
212
  WRAP-UP (user signals "done", "wrap up", "that's enough"):
177
- - [ ] 1. Consolidate journal if used \u2192 yg journal-archive
178
- - [ ] 2. yg drift --drifted-only \u2192 resolve
179
- - [ ] 3. yg validate \u2192 fix errors
180
- - [ ] 4. Report: which nodes and files were changed
213
+ - [ ] 1. yg drift --drifted-only \u2192 resolve
214
+ - [ ] 2. yg validate \u2192 fix errors
215
+ - [ ] 3. Report: which nodes and files were changed
181
216
 
182
217
  BEFORE ENDING ANY RESPONSE (self-audit):
218
+ - [ ] Did I interact with mapped code (read, research, or modify)? If yes \u2192 did I use a graph tool BEFORE reading source?
183
219
  - [ ] Did I modify source code? If yes \u2192 did I update graph artifacts in this same response?
184
- - [ ] If you changed code and did not sync the graph, you have broken the protocol. Do not finish until both are done.
220
+ - [ ] If you broke either rule, you have broken the protocol. Do not finish until both are fixed.
185
221
  \`\`\`
186
222
 
187
223
  ### Modify Source Code
@@ -319,8 +355,7 @@ var KNOWLEDGE_BASE = `## KNOWLEDGE BASE
319
355
  aspects/ \u2190 what must: cross-cutting requirements with rationale and guidance
320
356
  flows/ \u2190 why and in what process: business processes with node participation
321
357
  schemas/ \u2190 YAML schemas \u2014 read before creating any graph element
322
- .drift-state \u2190 generated by CLI; never edit manually
323
- .journal.yaml \u2190 generated by CLI; never edit manually
358
+ .drift-state/ \u2190 generated by CLI; never edit manually
324
359
  \`\`\`
325
360
 
326
361
  Key facts:
@@ -413,7 +448,7 @@ Test: "Does this describe what happens in the world, or only in the software?" I
413
448
  ### CLI Reference
414
449
 
415
450
  \`\`\`
416
- yg preflight [--quick] Unified diagnostic: journal + drift + status + validate.
451
+ yg preflight [--quick] Unified diagnostic: drift + status + validate.
417
452
  yg owner --file <path> Find the node that owns this file.
418
453
  yg build-context --node <path> Assemble context package for this node.
419
454
  yg tree [--root <path>] [--depth N] Print graph structure.
@@ -431,10 +466,6 @@ yg drift [--scope <path>|all] [--drifted-only] [--limit <n>]
431
466
  Detect source and graph drift (bidirectional).
432
467
  yg drift-sync --node <path> [--recursive] | --all
433
468
  Record file hashes as new baseline.
434
- yg journal-read Read pending journal entries.
435
- yg journal-add --note "<content>" [--target <node_path>]
436
- Add a journal entry.
437
- yg journal-archive Archive consolidated journal entries.
438
469
  \`\`\`
439
470
 
440
471
  ### Quick Routing Table
@@ -1000,10 +1031,7 @@ async function refreshSchemas(yggRoot) {
1000
1031
  } catch {
1001
1032
  }
1002
1033
  }
1003
- var GITIGNORE_CONTENT = `.journal.yaml
1004
- .drift-state
1005
- journals-archive/
1006
- `;
1034
+ var GITIGNORE_CONTENT = ``;
1007
1035
  function registerInitCommand(program2) {
1008
1036
  program2.command("init").description("Initialize Yggdrasil graph in current project").option(
1009
1037
  "--platform <name>",
@@ -2374,13 +2402,13 @@ function checkMappingOverlap(graph) {
2374
2402
  async function checkMappingPathsExist(graph) {
2375
2403
  const issues = [];
2376
2404
  const projectRoot = path11.dirname(graph.rootPath);
2377
- const { access: access5 } = await import("fs/promises");
2405
+ const { access: access4 } = await import("fs/promises");
2378
2406
  for (const [nodePath, node] of graph.nodes) {
2379
2407
  const mappingPaths = normalizeMappingPaths(node.meta.mapping);
2380
2408
  for (const mp of mappingPaths) {
2381
2409
  const absPath = path11.join(projectRoot, mp);
2382
2410
  try {
2383
- await access5(absPath);
2411
+ await access4(absPath);
2384
2412
  } catch {
2385
2413
  issues.push({
2386
2414
  severity: "warning",
@@ -2943,13 +2971,90 @@ ${errors.length} errors, ${warnings.length} warnings.
2943
2971
  import chalk2 from "chalk";
2944
2972
 
2945
2973
  // src/io/drift-state-store.ts
2946
- import { readFile as readFile14, writeFile as writeFile4 } from "fs/promises";
2974
+ import { readFile as readFile14, writeFile as writeFile4, stat as stat5, readdir as readdir6, mkdir as mkdir3, rm as rm2 } from "fs/promises";
2947
2975
  import path12 from "path";
2948
2976
  import { parse as yamlParse } from "yaml";
2949
- var DRIFT_STATE_FILE = ".drift-state";
2977
+ var DRIFT_STATE_DIR = ".drift-state";
2978
+ function nodeStatePath(yggRoot, nodePath) {
2979
+ return path12.join(yggRoot, DRIFT_STATE_DIR, `${nodePath}.json`);
2980
+ }
2981
+ async function scanJsonFiles(dir, baseDir) {
2982
+ const results = [];
2983
+ let entries;
2984
+ try {
2985
+ entries = await readdir6(dir, { withFileTypes: true });
2986
+ } catch {
2987
+ return results;
2988
+ }
2989
+ for (const entry of entries) {
2990
+ const fullPath = path12.join(dir, entry.name);
2991
+ if (entry.isDirectory()) {
2992
+ const nested = await scanJsonFiles(fullPath, baseDir);
2993
+ results.push(...nested);
2994
+ } else if (entry.isFile() && entry.name.endsWith(".json")) {
2995
+ const relPath = path12.relative(baseDir, fullPath);
2996
+ const nodePath = relPath.replace(/\\/g, "/").replace(/\.json$/, "");
2997
+ results.push(nodePath);
2998
+ }
2999
+ }
3000
+ return results;
3001
+ }
3002
+ async function removeEmptyParents(filePath, stopDir) {
3003
+ let dir = path12.dirname(filePath);
3004
+ while (dir !== stopDir && dir.startsWith(stopDir)) {
3005
+ try {
3006
+ const entries = await readdir6(dir);
3007
+ if (entries.length === 0) {
3008
+ await rm2(dir, { recursive: true });
3009
+ dir = path12.dirname(dir);
3010
+ } else {
3011
+ break;
3012
+ }
3013
+ } catch {
3014
+ break;
3015
+ }
3016
+ }
3017
+ }
3018
+ async function readNodeDriftState(yggRoot, nodePath) {
3019
+ try {
3020
+ const filePath = nodeStatePath(yggRoot, nodePath);
3021
+ const content = await readFile14(filePath, "utf-8");
3022
+ const parsed = JSON.parse(content);
3023
+ return parsed;
3024
+ } catch {
3025
+ return void 0;
3026
+ }
3027
+ }
3028
+ async function writeNodeDriftState(yggRoot, nodePath, nodeState) {
3029
+ const filePath = nodeStatePath(yggRoot, nodePath);
3030
+ await mkdir3(path12.dirname(filePath), { recursive: true });
3031
+ const content = JSON.stringify(nodeState, null, 2) + "\n";
3032
+ await writeFile4(filePath, content, "utf-8");
3033
+ }
3034
+ async function garbageCollectDriftState(yggRoot, validNodePaths) {
3035
+ const driftDir = path12.join(yggRoot, DRIFT_STATE_DIR);
3036
+ const allNodePaths = await scanJsonFiles(driftDir, driftDir);
3037
+ const removed = [];
3038
+ for (const nodePath of allNodePaths) {
3039
+ if (!validNodePaths.has(nodePath)) {
3040
+ const filePath = nodeStatePath(yggRoot, nodePath);
3041
+ await rm2(filePath);
3042
+ await removeEmptyParents(filePath, driftDir);
3043
+ removed.push(nodePath);
3044
+ }
3045
+ }
3046
+ return removed.sort();
3047
+ }
2950
3048
  async function readDriftState(yggRoot) {
3049
+ const driftPath = path12.join(yggRoot, DRIFT_STATE_DIR);
3050
+ let driftStat;
2951
3051
  try {
2952
- const content = await readFile14(path12.join(yggRoot, DRIFT_STATE_FILE), "utf-8");
3052
+ driftStat = await stat5(driftPath);
3053
+ } catch {
3054
+ return {};
3055
+ }
3056
+ if (driftStat.isFile()) {
3057
+ const content = await readFile14(driftPath, "utf-8");
2953
3058
  let raw;
2954
3059
  try {
2955
3060
  raw = JSON.parse(content);
@@ -2957,24 +3062,31 @@ async function readDriftState(yggRoot) {
2957
3062
  raw = yamlParse(content);
2958
3063
  }
2959
3064
  if (!raw || typeof raw !== "object") return {};
2960
- const state = {};
3065
+ const state2 = {};
2961
3066
  for (const [key, value] of Object.entries(raw)) {
2962
3067
  if (typeof value === "object" && value !== null && "hash" in value) {
2963
- state[key] = value;
3068
+ state2[key] = value;
2964
3069
  }
2965
3070
  }
2966
- return state;
2967
- } catch {
2968
- return {};
3071
+ await rm2(driftPath);
3072
+ for (const [nodePath, nodeState] of Object.entries(state2)) {
3073
+ await writeNodeDriftState(yggRoot, nodePath, nodeState);
3074
+ }
3075
+ return state2;
2969
3076
  }
2970
- }
2971
- async function writeDriftState(yggRoot, state) {
2972
- const content = JSON.stringify(state);
2973
- await writeFile4(path12.join(yggRoot, DRIFT_STATE_FILE), content, "utf-8");
3077
+ const nodePaths = await scanJsonFiles(driftPath, driftPath);
3078
+ const state = {};
3079
+ for (const nodePath of nodePaths) {
3080
+ const nodeState = await readNodeDriftState(yggRoot, nodePath);
3081
+ if (nodeState) {
3082
+ state[nodePath] = nodeState;
3083
+ }
3084
+ }
3085
+ return state;
2974
3086
  }
2975
3087
 
2976
3088
  // src/utils/hash.ts
2977
- import { readFile as readFile15, readdir as readdir6, stat as stat5 } from "fs/promises";
3089
+ import { readFile as readFile15, readdir as readdir7, stat as stat6 } from "fs/promises";
2978
3090
  import path13 from "path";
2979
3091
  import { createHash } from "crypto";
2980
3092
  import { createRequire } from "module";
@@ -3014,7 +3126,7 @@ async function hashTrackedFiles(projectRoot, trackedFiles, storedFileData, exclu
3014
3126
  for (const tf of trackedFiles) {
3015
3127
  const absPath = path13.join(projectRoot, tf.path);
3016
3128
  try {
3017
- const st = await stat5(absPath);
3129
+ const st = await stat6(absPath);
3018
3130
  if (st.isDirectory()) {
3019
3131
  const dirEntries = await collectDirectoryFilePaths(absPath, absPath, {
3020
3132
  projectRoot,
@@ -3068,7 +3180,7 @@ async function collectDirectoryFilePaths(directoryPath, rootDirectoryPath, optio
3068
3180
  stack = [...stack, { basePath: directoryPath, matcher: localMatcher }];
3069
3181
  } catch {
3070
3182
  }
3071
- const entries = await readdir6(directoryPath, { withFileTypes: true });
3183
+ const entries = await readdir7(directoryPath, { withFileTypes: true });
3072
3184
  const dirs = [];
3073
3185
  const files = [];
3074
3186
  for (const entry of entries) {
@@ -3083,7 +3195,7 @@ async function collectDirectoryFilePaths(directoryPath, rootDirectoryPath, optio
3083
3195
  gitignoreStack: stack
3084
3196
  }))),
3085
3197
  Promise.all(files.map(async (f) => {
3086
- const fileStat = await stat5(f);
3198
+ const fileStat = await stat6(f);
3087
3199
  return {
3088
3200
  relPath: path13.relative(rootDirectoryPath, f),
3089
3201
  absPath: f,
@@ -3316,18 +3428,15 @@ async function syncDriftState(graph, nodePath) {
3316
3428
  if (!node.meta.mapping) throw new Error(`Node has no mapping: ${nodePath}`);
3317
3429
  const trackedFiles = collectTrackedFiles(node, graph);
3318
3430
  const excludePrefixes = getChildMappingExclusions(graph, nodePath);
3319
- const existingState = await readDriftState(graph.rootPath);
3320
- const existingEntry = existingState[nodePath];
3431
+ const existingEntry = await readNodeDriftState(graph.rootPath, nodePath);
3321
3432
  const storedFileData = existingEntry?.files ? { hashes: existingEntry.files, mtimes: existingEntry.mtimes ?? {} } : void 0;
3322
3433
  const { canonicalHash, fileHashes, fileMtimes } = await hashTrackedFiles(projectRoot, trackedFiles, storedFileData, excludePrefixes);
3323
3434
  const previousHash = existingEntry?.hash;
3324
- existingState[nodePath] = { hash: canonicalHash, files: fileHashes, mtimes: fileMtimes };
3325
- for (const key of Object.keys(existingState)) {
3326
- if (!graph.nodes.has(key)) {
3327
- delete existingState[key];
3328
- }
3329
- }
3330
- await writeDriftState(graph.rootPath, existingState);
3435
+ await writeNodeDriftState(graph.rootPath, nodePath, {
3436
+ hash: canonicalHash,
3437
+ files: fileHashes,
3438
+ mtimes: fileMtimes
3439
+ });
3331
3440
  return { previousHash, currentHash: canonicalHash };
3332
3441
  }
3333
3442
 
@@ -3509,6 +3618,14 @@ function registerDriftSyncCommand(program2) {
3509
3618
  `
3510
3619
  );
3511
3620
  }
3621
+ if (options.all) {
3622
+ const validPaths = new Set(nodesToSync);
3623
+ const removed = await garbageCollectDriftState(graph.rootPath, validPaths);
3624
+ for (const r of removed) {
3625
+ process.stdout.write(chalk3.dim(`Removed orphaned drift state: ${r}
3626
+ `));
3627
+ }
3628
+ }
3512
3629
  } catch (error) {
3513
3630
  process.stderr.write(`Error: ${error.message}
3514
3631
  `);
@@ -3625,10 +3742,10 @@ function registerTreeCommand(program2) {
3625
3742
  let roots;
3626
3743
  let showProjectName;
3627
3744
  if (options.root?.trim()) {
3628
- const path20 = options.root.trim().replace(/\/$/, "");
3629
- const node = graph.nodes.get(path20);
3745
+ const path19 = options.root.trim().replace(/\/$/, "");
3746
+ const node = graph.nodes.get(path19);
3630
3747
  if (!node) {
3631
- process.stderr.write(`Error: path '${path20}' not found
3748
+ process.stderr.write(`Error: path '${path19}' not found
3632
3749
  `);
3633
3750
  process.exit(1);
3634
3751
  }
@@ -3815,7 +3932,7 @@ function registerDepsCommand(program2) {
3815
3932
  }
3816
3933
 
3817
3934
  // src/core/graph-from-git.ts
3818
- import { mkdtemp, rm as rm2 } from "fs/promises";
3935
+ import { mkdtemp, rm as rm3 } from "fs/promises";
3819
3936
  import { tmpdir } from "os";
3820
3937
  import path18 from "path";
3821
3938
  import { execSync as execSync2 } from "child_process";
@@ -3841,7 +3958,7 @@ async function loadGraphFromRef(projectRoot, ref = "HEAD") {
3841
3958
  return null;
3842
3959
  } finally {
3843
3960
  if (tmpDir) {
3844
- await rm2(tmpDir, { recursive: true, force: true });
3961
+ await rm3(tmpDir, { recursive: true, force: true });
3845
3962
  }
3846
3963
  }
3847
3964
  }
@@ -3899,14 +4016,14 @@ function buildTransitiveChains(targetNode, direct, allDependents, reverse) {
3899
4016
  }
3900
4017
  const chains = [];
3901
4018
  for (const node of transitiveOnly) {
3902
- const path20 = [];
4019
+ const path19 = [];
3903
4020
  let current = node;
3904
4021
  while (current) {
3905
- path20.unshift(current);
4022
+ path19.unshift(current);
3906
4023
  current = parent.get(current);
3907
4024
  }
3908
- if (path20.length >= 3) {
3909
- chains.push(path20.slice(1).map((p) => `<- ${p}`).join(" "));
4025
+ if (path19.length >= 3) {
4026
+ chains.push(path19.slice(1).map((p) => `<- ${p}`).join(" "));
3910
4027
  }
3911
4028
  }
3912
4029
  return chains.sort();
@@ -4327,131 +4444,12 @@ function registerFlowsCommand(program2) {
4327
4444
  });
4328
4445
  }
4329
4446
 
4330
- // src/io/journal-store.ts
4331
- import { readFile as readFile16, writeFile as writeFile5, mkdir as mkdir3, rename as rename2, access as access4 } from "fs/promises";
4332
- import { parse as parseYaml8, stringify as stringifyYaml2 } from "yaml";
4333
- import path19 from "path";
4334
- var JOURNAL_FILE = ".journal.yaml";
4335
- var ARCHIVE_DIR = "journals-archive";
4336
- async function readJournal(yggRoot) {
4337
- const filePath = path19.join(yggRoot, JOURNAL_FILE);
4338
- try {
4339
- const content = await readFile16(filePath, "utf-8");
4340
- const raw = parseYaml8(content);
4341
- const entries = raw.entries ?? [];
4342
- return Array.isArray(entries) ? entries : [];
4343
- } catch {
4344
- return [];
4345
- }
4346
- }
4347
- async function appendJournalEntry(yggRoot, note, target) {
4348
- const entries = await readJournal(yggRoot);
4349
- const at = (/* @__PURE__ */ new Date()).toISOString();
4350
- const entry = target ? { at, target, note } : { at, note };
4351
- entries.push(entry);
4352
- const filePath = path19.join(yggRoot, JOURNAL_FILE);
4353
- const content = stringifyYaml2({ entries });
4354
- await writeFile5(filePath, content, "utf-8");
4355
- return entry;
4356
- }
4357
- async function archiveJournal(yggRoot) {
4358
- const journalPath = path19.join(yggRoot, JOURNAL_FILE);
4359
- try {
4360
- await access4(journalPath);
4361
- } catch {
4362
- return null;
4363
- }
4364
- const entries = await readJournal(yggRoot);
4365
- if (entries.length === 0) return null;
4366
- const archiveDir = path19.join(yggRoot, ARCHIVE_DIR);
4367
- await mkdir3(archiveDir, { recursive: true });
4368
- const now = /* @__PURE__ */ new Date();
4369
- const timestamp = `${now.getUTCFullYear()}${String(now.getUTCMonth() + 1).padStart(2, "0")}${String(now.getUTCDate()).padStart(2, "0")}-${String(now.getUTCHours()).padStart(2, "0")}${String(now.getUTCMinutes()).padStart(2, "0")}${String(now.getUTCSeconds()).padStart(2, "0")}`;
4370
- const archiveName = `.journal.${timestamp}.yaml`;
4371
- const archivePath = path19.join(archiveDir, archiveName);
4372
- await rename2(journalPath, archivePath);
4373
- return { archiveName, entryCount: entries.length };
4374
- }
4375
-
4376
- // src/cli/journal-add.ts
4377
- function registerJournalAddCommand(program2) {
4378
- program2.command("journal-add").description("Add a note to the session journal").requiredOption("--note <text>", "Note content").option("--target <node-path>", "Node path this note relates to").action(async (options) => {
4379
- try {
4380
- const projectRoot = process.cwd();
4381
- const yggRoot = await findYggRoot(projectRoot);
4382
- await appendJournalEntry(yggRoot, options.note, options.target);
4383
- const entries = await readJournal(yggRoot);
4384
- process.stdout.write(`Note added to journal (${entries.length} entries total)
4385
- `);
4386
- } catch (error) {
4387
- process.stderr.write(`Error: ${error.message}
4388
- `);
4389
- process.exit(1);
4390
- }
4391
- });
4392
- }
4393
-
4394
- // src/cli/journal-read.ts
4395
- function registerJournalReadCommand(program2) {
4396
- program2.command("journal-read").description("List pending journal entries").action(async () => {
4397
- try {
4398
- const projectRoot = process.cwd();
4399
- const yggRoot = await findYggRoot(projectRoot);
4400
- const entries = await readJournal(yggRoot);
4401
- if (entries.length === 0) {
4402
- process.stdout.write("Session journal: empty (clean state)\n");
4403
- return;
4404
- }
4405
- process.stdout.write(`Session journal (${entries.length} entries):
4406
-
4407
- `);
4408
- for (const e of entries) {
4409
- const date = e.at.slice(0, 19).replace("T", " ");
4410
- const target = e.target ? ` ${e.target}` : "";
4411
- process.stdout.write(`[${date}]${target}
4412
- ${e.note}
4413
-
4414
- `);
4415
- }
4416
- } catch (error) {
4417
- process.stderr.write(`Error: ${error.message}
4418
- `);
4419
- process.exit(1);
4420
- }
4421
- });
4422
- }
4423
-
4424
- // src/cli/journal-archive.ts
4425
- function registerJournalArchiveCommand(program2) {
4426
- program2.command("journal-archive").description("Archive journal after consolidating notes to graph").action(async () => {
4427
- try {
4428
- const projectRoot = process.cwd();
4429
- const yggRoot = await findYggRoot(projectRoot);
4430
- const result = await archiveJournal(yggRoot);
4431
- if (!result) {
4432
- process.stdout.write("No active journal - nothing to archive.\n");
4433
- return;
4434
- }
4435
- process.stdout.write(
4436
- `Archived journal (${result.entryCount} entries) -> journals-archive/${result.archiveName}
4437
- `
4438
- );
4439
- } catch (error) {
4440
- process.stderr.write(`Error: ${error.message}
4441
- `);
4442
- process.exit(1);
4443
- }
4444
- });
4445
- }
4446
-
4447
4447
  // src/cli/preflight.ts
4448
4448
  function registerPreflightCommand(program2) {
4449
- program2.command("preflight").description("Unified diagnostic report: journal, drift, status, validation").option("--quick", "Skip drift detection for faster results").action(async (options) => {
4449
+ program2.command("preflight").description("Unified diagnostic report: drift, status, validation").option("--quick", "Skip drift detection for faster results").action(async (options) => {
4450
4450
  try {
4451
4451
  const cwd = process.cwd();
4452
4452
  const graph = await loadGraph(cwd);
4453
- const yggRoot = await findYggRoot(cwd);
4454
- const journalEntries = await readJournal(yggRoot);
4455
4453
  const driftedEntries = options.quick ? [] : (await detectDrift(graph)).entries.filter((e) => e.status !== "ok");
4456
4454
  const nodeCount = graph.nodes.size;
4457
4455
  const aspectCount = graph.aspects.length;
@@ -4466,16 +4464,6 @@ function registerPreflightCommand(program2) {
4466
4464
  const lines = [];
4467
4465
  lines.push("=== Preflight Report ===");
4468
4466
  lines.push("");
4469
- if (journalEntries.length === 0) {
4470
- lines.push("Journal: clean");
4471
- } else {
4472
- lines.push(`Journal: ${journalEntries.length} pending entries`);
4473
- for (const entry of journalEntries) {
4474
- const target = entry.target ? ` [${entry.target}]` : "";
4475
- lines.push(` - ${entry.note}${target}`);
4476
- }
4477
- }
4478
- lines.push("");
4479
4467
  if (options.quick) {
4480
4468
  lines.push("Drift: skipped (--quick)");
4481
4469
  } else if (driftedEntries.length === 0) {
@@ -4512,7 +4500,7 @@ function registerPreflightCommand(program2) {
4512
4500
  }
4513
4501
  lines.push("");
4514
4502
  process.stdout.write(lines.join("\n"));
4515
- const hasIssues = journalEntries.length > 0 || !options.quick && driftedEntries.length > 0 || errors.length > 0;
4503
+ const hasIssues = !options.quick && driftedEntries.length > 0 || errors.length > 0;
4516
4504
  process.exit(hasIssues ? 1 : 0);
4517
4505
  } catch (error) {
4518
4506
  process.stderr.write(`Error: ${error.message}
@@ -4543,9 +4531,6 @@ registerDepsCommand(program);
4543
4531
  registerImpactCommand(program);
4544
4532
  registerAspectsCommand(program);
4545
4533
  registerFlowsCommand(program);
4546
- registerJournalAddCommand(program);
4547
- registerJournalReadCommand(program);
4548
- registerJournalArchiveCommand(program);
4549
4534
  registerPreflightCommand(program);
4550
4535
  program.parse();
4551
4536
  //# sourceMappingURL=bin.js.map