@chrisdudek/yg 2.0.0 → 2.1.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 +1 -7
- package/dist/bin.js +174 -195
- package/dist/bin.js.map +1 -1
- package/dist/templates/rules.ts +46 -21
- package/package.json +1 -1
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:
|
|
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,25 @@ import path from "path";
|
|
|
54
54
|
// src/templates/rules.ts
|
|
55
55
|
var CORE_PROTOCOL = `## CORE PROTOCOL
|
|
56
56
|
|
|
57
|
-
|
|
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:
|
|
63
71
|
1. yg owner --file <path>
|
|
64
|
-
2.
|
|
72
|
+
2. Choose the right graph tool for your task:
|
|
73
|
+
- Understanding how/why it works \u2192 yg build-context --node <owner>
|
|
74
|
+
- Assessing what is affected by a change \u2192 yg impact --node <owner>
|
|
75
|
+
- Planning modifications \u2192 both (build-context first, then impact)
|
|
65
76
|
The context package is your primary source of ARCHITECTURAL understanding:
|
|
66
77
|
intent, constraints, relations, rationale. For IMPLEMENTATION precision
|
|
67
78
|
(exact behavior, error handling, await patterns, edge cases) \u2014 verify
|
|
@@ -81,6 +92,8 @@ NEVER: modify code without graph coverage.
|
|
|
81
92
|
NEVER: read mapped source files to understand a component without
|
|
82
93
|
running yg build-context first \u2014 the graph captures intent,
|
|
83
94
|
constraints, and relations that source files cannot.
|
|
95
|
+
NEVER: assess blast radius of a change without running yg impact first
|
|
96
|
+
\u2014 the graph knows the dependency structure that grep cannot infer.
|
|
84
97
|
NEVER: invent rationale, business rules, or decisions.
|
|
85
98
|
NEVER: auto-resolve drift without asking the user.
|
|
86
99
|
WHEN UNSURE: ask the user. Never guess. Never assume.
|
|
@@ -88,7 +101,7 @@ WHEN UNSURE: ask the user. Never guess. Never assume.
|
|
|
88
101
|
|
|
89
102
|
### Five Core Rules
|
|
90
103
|
|
|
91
|
-
1. **Graph first.** Before reading, researching, planning, or modifying mapped files, run \`yg owner\` and \`yg build-context
|
|
104
|
+
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
105
|
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
106
|
3. **Never invent why.** The graph captures human intent. If you don't know why something was decided, ask. Never hallucinate rationale.
|
|
94
107
|
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 +109,22 @@ WHEN UNSURE: ask the user. Never guess. Never assume.
|
|
|
96
109
|
|
|
97
110
|
### Recognizing Graph-Required Actions
|
|
98
111
|
|
|
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
|
|
112
|
+
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
113
|
|
|
101
|
-
**Actions that require \`yg owner\` + \`yg build-context
|
|
114
|
+
**Actions that require \`yg owner\` + \`yg build-context\`:**
|
|
102
115
|
|
|
103
116
|
- Reading or exploring source files to understand a component
|
|
104
117
|
- Proposing approaches, designs, or plans for changing code
|
|
105
118
|
- Reviewing or debugging code
|
|
106
119
|
- Any form of reasoning about how mapped code works or should change
|
|
107
120
|
|
|
121
|
+
**Actions that require \`yg owner\` + \`yg impact\`:**
|
|
122
|
+
|
|
123
|
+
- Assessing blast radius before changing or removing a component
|
|
124
|
+
- Finding all dependents of a component
|
|
125
|
+
- Planning cross-cutting refactors or feature removals
|
|
126
|
+
- Scoping work that spans multiple nodes
|
|
127
|
+
|
|
108
128
|
**Actions that do NOT require yg:**
|
|
109
129
|
|
|
110
130
|
- Git operations (log, diff, status, blame)
|
|
@@ -112,11 +132,22 @@ What matters is the ACTION you are performing, not what instructed it. If the ac
|
|
|
112
132
|
- Running tests, builds, or linters
|
|
113
133
|
- Working with files that \`yg owner\` reports as unmapped
|
|
114
134
|
|
|
135
|
+
**Evasion patterns \u2014 if you think any of these, STOP:**
|
|
136
|
+
|
|
137
|
+
| Thought | Reality |
|
|
138
|
+
|---|---|
|
|
139
|
+
| "The skill/plan says to explore the codebase" | Exploring mapped code = yg owner + graph tool first |
|
|
140
|
+
| "I'm just scoping/searching, not understanding" | Scoping IS a graph action; use yg impact |
|
|
141
|
+
| "The plan step says to read this file" | Reading a mapped file = yg owner first |
|
|
142
|
+
| "I'm brainstorming, not implementing" | Brainstorming about mapped code needs graph context |
|
|
143
|
+
| "I'm only grepping for references" | Grep finds text; yg impact finds structural dependencies. Use both. |
|
|
144
|
+
| "I'll use the graph later when I modify" | Graph-first means BEFORE reading, not before modifying |
|
|
145
|
+
|
|
115
146
|
### Failure States
|
|
116
147
|
|
|
117
148
|
You have broken Yggdrasil if you do any of the following:
|
|
118
149
|
|
|
119
|
-
- \u274C Worked on a mapped file without running \`yg owner\` + \`
|
|
150
|
+
- \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
151
|
- \u274C Modified source code without updating graph artifacts in the same response, or vice versa.
|
|
121
152
|
- \u274C Resolved a code-graph inconsistency or ambiguity without asking the user first.
|
|
122
153
|
- \u274C Created or edited a graph element without reading its schema in \`schemas/\` first.
|
|
@@ -161,9 +192,8 @@ var OPERATIONS = `## OPERATIONS
|
|
|
161
192
|
\`\`\`
|
|
162
193
|
PREFLIGHT (every conversation, before any work):
|
|
163
194
|
- [ ] 1. yg preflight \u2192 read unified report
|
|
164
|
-
- [ ] 2. If
|
|
165
|
-
- [ ] 3. If
|
|
166
|
-
- [ ] 4. If validation errors: fix, re-run yg validate
|
|
195
|
+
- [ ] 2. If drift: resolve per Drift Resolution, then yg drift-sync per node
|
|
196
|
+
- [ ] 3. If validation errors: fix, re-run yg validate
|
|
167
197
|
Exception: read-only requests (explain, analyze) \u2014 skip preflight.
|
|
168
198
|
|
|
169
199
|
UNDERSTANDING mapped code (questions, research, OR planning):
|
|
@@ -174,14 +204,14 @@ UNDERSTANDING mapped code (questions, research, OR planning):
|
|
|
174
204
|
Raw reads supplement the context package \u2014 they do not replace it.
|
|
175
205
|
|
|
176
206
|
WRAP-UP (user signals "done", "wrap up", "that's enough"):
|
|
177
|
-
- [ ] 1.
|
|
178
|
-
- [ ] 2. yg
|
|
179
|
-
- [ ] 3.
|
|
180
|
-
- [ ] 4. Report: which nodes and files were changed
|
|
207
|
+
- [ ] 1. yg drift --drifted-only \u2192 resolve
|
|
208
|
+
- [ ] 2. yg validate \u2192 fix errors
|
|
209
|
+
- [ ] 3. Report: which nodes and files were changed
|
|
181
210
|
|
|
182
211
|
BEFORE ENDING ANY RESPONSE (self-audit):
|
|
212
|
+
- [ ] Did I interact with mapped code (read, research, or modify)? If yes \u2192 did I use a graph tool BEFORE reading source?
|
|
183
213
|
- [ ] Did I modify source code? If yes \u2192 did I update graph artifacts in this same response?
|
|
184
|
-
- [ ] If you
|
|
214
|
+
- [ ] If you broke either rule, you have broken the protocol. Do not finish until both are fixed.
|
|
185
215
|
\`\`\`
|
|
186
216
|
|
|
187
217
|
### Modify Source Code
|
|
@@ -319,8 +349,7 @@ var KNOWLEDGE_BASE = `## KNOWLEDGE BASE
|
|
|
319
349
|
aspects/ \u2190 what must: cross-cutting requirements with rationale and guidance
|
|
320
350
|
flows/ \u2190 why and in what process: business processes with node participation
|
|
321
351
|
schemas/ \u2190 YAML schemas \u2014 read before creating any graph element
|
|
322
|
-
.drift-state
|
|
323
|
-
.journal.yaml \u2190 generated by CLI; never edit manually
|
|
352
|
+
.drift-state/ \u2190 generated by CLI; never edit manually
|
|
324
353
|
\`\`\`
|
|
325
354
|
|
|
326
355
|
Key facts:
|
|
@@ -413,7 +442,7 @@ Test: "Does this describe what happens in the world, or only in the software?" I
|
|
|
413
442
|
### CLI Reference
|
|
414
443
|
|
|
415
444
|
\`\`\`
|
|
416
|
-
yg preflight [--quick] Unified diagnostic:
|
|
445
|
+
yg preflight [--quick] Unified diagnostic: drift + status + validate.
|
|
417
446
|
yg owner --file <path> Find the node that owns this file.
|
|
418
447
|
yg build-context --node <path> Assemble context package for this node.
|
|
419
448
|
yg tree [--root <path>] [--depth N] Print graph structure.
|
|
@@ -431,10 +460,6 @@ yg drift [--scope <path>|all] [--drifted-only] [--limit <n>]
|
|
|
431
460
|
Detect source and graph drift (bidirectional).
|
|
432
461
|
yg drift-sync --node <path> [--recursive] | --all
|
|
433
462
|
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
463
|
\`\`\`
|
|
439
464
|
|
|
440
465
|
### Quick Routing Table
|
|
@@ -1000,10 +1025,7 @@ async function refreshSchemas(yggRoot) {
|
|
|
1000
1025
|
} catch {
|
|
1001
1026
|
}
|
|
1002
1027
|
}
|
|
1003
|
-
var GITIGNORE_CONTENT =
|
|
1004
|
-
.drift-state
|
|
1005
|
-
journals-archive/
|
|
1006
|
-
`;
|
|
1028
|
+
var GITIGNORE_CONTENT = ``;
|
|
1007
1029
|
function registerInitCommand(program2) {
|
|
1008
1030
|
program2.command("init").description("Initialize Yggdrasil graph in current project").option(
|
|
1009
1031
|
"--platform <name>",
|
|
@@ -2374,13 +2396,13 @@ function checkMappingOverlap(graph) {
|
|
|
2374
2396
|
async function checkMappingPathsExist(graph) {
|
|
2375
2397
|
const issues = [];
|
|
2376
2398
|
const projectRoot = path11.dirname(graph.rootPath);
|
|
2377
|
-
const { access:
|
|
2399
|
+
const { access: access4 } = await import("fs/promises");
|
|
2378
2400
|
for (const [nodePath, node] of graph.nodes) {
|
|
2379
2401
|
const mappingPaths = normalizeMappingPaths(node.meta.mapping);
|
|
2380
2402
|
for (const mp of mappingPaths) {
|
|
2381
2403
|
const absPath = path11.join(projectRoot, mp);
|
|
2382
2404
|
try {
|
|
2383
|
-
await
|
|
2405
|
+
await access4(absPath);
|
|
2384
2406
|
} catch {
|
|
2385
2407
|
issues.push({
|
|
2386
2408
|
severity: "warning",
|
|
@@ -2943,13 +2965,90 @@ ${errors.length} errors, ${warnings.length} warnings.
|
|
|
2943
2965
|
import chalk2 from "chalk";
|
|
2944
2966
|
|
|
2945
2967
|
// src/io/drift-state-store.ts
|
|
2946
|
-
import { readFile as readFile14, writeFile as writeFile4 } from "fs/promises";
|
|
2968
|
+
import { readFile as readFile14, writeFile as writeFile4, stat as stat5, readdir as readdir6, mkdir as mkdir3, rm as rm2 } from "fs/promises";
|
|
2947
2969
|
import path12 from "path";
|
|
2948
2970
|
import { parse as yamlParse } from "yaml";
|
|
2949
|
-
var
|
|
2971
|
+
var DRIFT_STATE_DIR = ".drift-state";
|
|
2972
|
+
function nodeStatePath(yggRoot, nodePath) {
|
|
2973
|
+
return path12.join(yggRoot, DRIFT_STATE_DIR, `${nodePath}.json`);
|
|
2974
|
+
}
|
|
2975
|
+
async function scanJsonFiles(dir, baseDir) {
|
|
2976
|
+
const results = [];
|
|
2977
|
+
let entries;
|
|
2978
|
+
try {
|
|
2979
|
+
entries = await readdir6(dir, { withFileTypes: true });
|
|
2980
|
+
} catch {
|
|
2981
|
+
return results;
|
|
2982
|
+
}
|
|
2983
|
+
for (const entry of entries) {
|
|
2984
|
+
const fullPath = path12.join(dir, entry.name);
|
|
2985
|
+
if (entry.isDirectory()) {
|
|
2986
|
+
const nested = await scanJsonFiles(fullPath, baseDir);
|
|
2987
|
+
results.push(...nested);
|
|
2988
|
+
} else if (entry.isFile() && entry.name.endsWith(".json")) {
|
|
2989
|
+
const relPath = path12.relative(baseDir, fullPath);
|
|
2990
|
+
const nodePath = relPath.replace(/\\/g, "/").replace(/\.json$/, "");
|
|
2991
|
+
results.push(nodePath);
|
|
2992
|
+
}
|
|
2993
|
+
}
|
|
2994
|
+
return results;
|
|
2995
|
+
}
|
|
2996
|
+
async function removeEmptyParents(filePath, stopDir) {
|
|
2997
|
+
let dir = path12.dirname(filePath);
|
|
2998
|
+
while (dir !== stopDir && dir.startsWith(stopDir)) {
|
|
2999
|
+
try {
|
|
3000
|
+
const entries = await readdir6(dir);
|
|
3001
|
+
if (entries.length === 0) {
|
|
3002
|
+
await rm2(dir, { recursive: true });
|
|
3003
|
+
dir = path12.dirname(dir);
|
|
3004
|
+
} else {
|
|
3005
|
+
break;
|
|
3006
|
+
}
|
|
3007
|
+
} catch {
|
|
3008
|
+
break;
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
}
|
|
3012
|
+
async function readNodeDriftState(yggRoot, nodePath) {
|
|
3013
|
+
try {
|
|
3014
|
+
const filePath = nodeStatePath(yggRoot, nodePath);
|
|
3015
|
+
const content = await readFile14(filePath, "utf-8");
|
|
3016
|
+
const parsed = JSON.parse(content);
|
|
3017
|
+
return parsed;
|
|
3018
|
+
} catch {
|
|
3019
|
+
return void 0;
|
|
3020
|
+
}
|
|
3021
|
+
}
|
|
3022
|
+
async function writeNodeDriftState(yggRoot, nodePath, nodeState) {
|
|
3023
|
+
const filePath = nodeStatePath(yggRoot, nodePath);
|
|
3024
|
+
await mkdir3(path12.dirname(filePath), { recursive: true });
|
|
3025
|
+
const content = JSON.stringify(nodeState, null, 2) + "\n";
|
|
3026
|
+
await writeFile4(filePath, content, "utf-8");
|
|
3027
|
+
}
|
|
3028
|
+
async function garbageCollectDriftState(yggRoot, validNodePaths) {
|
|
3029
|
+
const driftDir = path12.join(yggRoot, DRIFT_STATE_DIR);
|
|
3030
|
+
const allNodePaths = await scanJsonFiles(driftDir, driftDir);
|
|
3031
|
+
const removed = [];
|
|
3032
|
+
for (const nodePath of allNodePaths) {
|
|
3033
|
+
if (!validNodePaths.has(nodePath)) {
|
|
3034
|
+
const filePath = nodeStatePath(yggRoot, nodePath);
|
|
3035
|
+
await rm2(filePath);
|
|
3036
|
+
await removeEmptyParents(filePath, driftDir);
|
|
3037
|
+
removed.push(nodePath);
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
3040
|
+
return removed.sort();
|
|
3041
|
+
}
|
|
2950
3042
|
async function readDriftState(yggRoot) {
|
|
3043
|
+
const driftPath = path12.join(yggRoot, DRIFT_STATE_DIR);
|
|
3044
|
+
let driftStat;
|
|
2951
3045
|
try {
|
|
2952
|
-
|
|
3046
|
+
driftStat = await stat5(driftPath);
|
|
3047
|
+
} catch {
|
|
3048
|
+
return {};
|
|
3049
|
+
}
|
|
3050
|
+
if (driftStat.isFile()) {
|
|
3051
|
+
const content = await readFile14(driftPath, "utf-8");
|
|
2953
3052
|
let raw;
|
|
2954
3053
|
try {
|
|
2955
3054
|
raw = JSON.parse(content);
|
|
@@ -2957,24 +3056,31 @@ async function readDriftState(yggRoot) {
|
|
|
2957
3056
|
raw = yamlParse(content);
|
|
2958
3057
|
}
|
|
2959
3058
|
if (!raw || typeof raw !== "object") return {};
|
|
2960
|
-
const
|
|
3059
|
+
const state2 = {};
|
|
2961
3060
|
for (const [key, value] of Object.entries(raw)) {
|
|
2962
3061
|
if (typeof value === "object" && value !== null && "hash" in value) {
|
|
2963
|
-
|
|
3062
|
+
state2[key] = value;
|
|
2964
3063
|
}
|
|
2965
3064
|
}
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
3065
|
+
await rm2(driftPath);
|
|
3066
|
+
for (const [nodePath, nodeState] of Object.entries(state2)) {
|
|
3067
|
+
await writeNodeDriftState(yggRoot, nodePath, nodeState);
|
|
3068
|
+
}
|
|
3069
|
+
return state2;
|
|
2969
3070
|
}
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
const
|
|
2973
|
-
|
|
3071
|
+
const nodePaths = await scanJsonFiles(driftPath, driftPath);
|
|
3072
|
+
const state = {};
|
|
3073
|
+
for (const nodePath of nodePaths) {
|
|
3074
|
+
const nodeState = await readNodeDriftState(yggRoot, nodePath);
|
|
3075
|
+
if (nodeState) {
|
|
3076
|
+
state[nodePath] = nodeState;
|
|
3077
|
+
}
|
|
3078
|
+
}
|
|
3079
|
+
return state;
|
|
2974
3080
|
}
|
|
2975
3081
|
|
|
2976
3082
|
// src/utils/hash.ts
|
|
2977
|
-
import { readFile as readFile15, readdir as
|
|
3083
|
+
import { readFile as readFile15, readdir as readdir7, stat as stat6 } from "fs/promises";
|
|
2978
3084
|
import path13 from "path";
|
|
2979
3085
|
import { createHash } from "crypto";
|
|
2980
3086
|
import { createRequire } from "module";
|
|
@@ -3014,7 +3120,7 @@ async function hashTrackedFiles(projectRoot, trackedFiles, storedFileData, exclu
|
|
|
3014
3120
|
for (const tf of trackedFiles) {
|
|
3015
3121
|
const absPath = path13.join(projectRoot, tf.path);
|
|
3016
3122
|
try {
|
|
3017
|
-
const st = await
|
|
3123
|
+
const st = await stat6(absPath);
|
|
3018
3124
|
if (st.isDirectory()) {
|
|
3019
3125
|
const dirEntries = await collectDirectoryFilePaths(absPath, absPath, {
|
|
3020
3126
|
projectRoot,
|
|
@@ -3068,7 +3174,7 @@ async function collectDirectoryFilePaths(directoryPath, rootDirectoryPath, optio
|
|
|
3068
3174
|
stack = [...stack, { basePath: directoryPath, matcher: localMatcher }];
|
|
3069
3175
|
} catch {
|
|
3070
3176
|
}
|
|
3071
|
-
const entries = await
|
|
3177
|
+
const entries = await readdir7(directoryPath, { withFileTypes: true });
|
|
3072
3178
|
const dirs = [];
|
|
3073
3179
|
const files = [];
|
|
3074
3180
|
for (const entry of entries) {
|
|
@@ -3083,7 +3189,7 @@ async function collectDirectoryFilePaths(directoryPath, rootDirectoryPath, optio
|
|
|
3083
3189
|
gitignoreStack: stack
|
|
3084
3190
|
}))),
|
|
3085
3191
|
Promise.all(files.map(async (f) => {
|
|
3086
|
-
const fileStat = await
|
|
3192
|
+
const fileStat = await stat6(f);
|
|
3087
3193
|
return {
|
|
3088
3194
|
relPath: path13.relative(rootDirectoryPath, f),
|
|
3089
3195
|
absPath: f,
|
|
@@ -3316,18 +3422,15 @@ async function syncDriftState(graph, nodePath) {
|
|
|
3316
3422
|
if (!node.meta.mapping) throw new Error(`Node has no mapping: ${nodePath}`);
|
|
3317
3423
|
const trackedFiles = collectTrackedFiles(node, graph);
|
|
3318
3424
|
const excludePrefixes = getChildMappingExclusions(graph, nodePath);
|
|
3319
|
-
const
|
|
3320
|
-
const existingEntry = existingState[nodePath];
|
|
3425
|
+
const existingEntry = await readNodeDriftState(graph.rootPath, nodePath);
|
|
3321
3426
|
const storedFileData = existingEntry?.files ? { hashes: existingEntry.files, mtimes: existingEntry.mtimes ?? {} } : void 0;
|
|
3322
3427
|
const { canonicalHash, fileHashes, fileMtimes } = await hashTrackedFiles(projectRoot, trackedFiles, storedFileData, excludePrefixes);
|
|
3323
3428
|
const previousHash = existingEntry?.hash;
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
}
|
|
3330
|
-
await writeDriftState(graph.rootPath, existingState);
|
|
3429
|
+
await writeNodeDriftState(graph.rootPath, nodePath, {
|
|
3430
|
+
hash: canonicalHash,
|
|
3431
|
+
files: fileHashes,
|
|
3432
|
+
mtimes: fileMtimes
|
|
3433
|
+
});
|
|
3331
3434
|
return { previousHash, currentHash: canonicalHash };
|
|
3332
3435
|
}
|
|
3333
3436
|
|
|
@@ -3509,6 +3612,14 @@ function registerDriftSyncCommand(program2) {
|
|
|
3509
3612
|
`
|
|
3510
3613
|
);
|
|
3511
3614
|
}
|
|
3615
|
+
if (options.all) {
|
|
3616
|
+
const validPaths = new Set(nodesToSync);
|
|
3617
|
+
const removed = await garbageCollectDriftState(graph.rootPath, validPaths);
|
|
3618
|
+
for (const r of removed) {
|
|
3619
|
+
process.stdout.write(chalk3.dim(`Removed orphaned drift state: ${r}
|
|
3620
|
+
`));
|
|
3621
|
+
}
|
|
3622
|
+
}
|
|
3512
3623
|
} catch (error) {
|
|
3513
3624
|
process.stderr.write(`Error: ${error.message}
|
|
3514
3625
|
`);
|
|
@@ -3625,10 +3736,10 @@ function registerTreeCommand(program2) {
|
|
|
3625
3736
|
let roots;
|
|
3626
3737
|
let showProjectName;
|
|
3627
3738
|
if (options.root?.trim()) {
|
|
3628
|
-
const
|
|
3629
|
-
const node = graph.nodes.get(
|
|
3739
|
+
const path19 = options.root.trim().replace(/\/$/, "");
|
|
3740
|
+
const node = graph.nodes.get(path19);
|
|
3630
3741
|
if (!node) {
|
|
3631
|
-
process.stderr.write(`Error: path '${
|
|
3742
|
+
process.stderr.write(`Error: path '${path19}' not found
|
|
3632
3743
|
`);
|
|
3633
3744
|
process.exit(1);
|
|
3634
3745
|
}
|
|
@@ -3815,7 +3926,7 @@ function registerDepsCommand(program2) {
|
|
|
3815
3926
|
}
|
|
3816
3927
|
|
|
3817
3928
|
// src/core/graph-from-git.ts
|
|
3818
|
-
import { mkdtemp, rm as
|
|
3929
|
+
import { mkdtemp, rm as rm3 } from "fs/promises";
|
|
3819
3930
|
import { tmpdir } from "os";
|
|
3820
3931
|
import path18 from "path";
|
|
3821
3932
|
import { execSync as execSync2 } from "child_process";
|
|
@@ -3841,7 +3952,7 @@ async function loadGraphFromRef(projectRoot, ref = "HEAD") {
|
|
|
3841
3952
|
return null;
|
|
3842
3953
|
} finally {
|
|
3843
3954
|
if (tmpDir) {
|
|
3844
|
-
await
|
|
3955
|
+
await rm3(tmpDir, { recursive: true, force: true });
|
|
3845
3956
|
}
|
|
3846
3957
|
}
|
|
3847
3958
|
}
|
|
@@ -3899,14 +4010,14 @@ function buildTransitiveChains(targetNode, direct, allDependents, reverse) {
|
|
|
3899
4010
|
}
|
|
3900
4011
|
const chains = [];
|
|
3901
4012
|
for (const node of transitiveOnly) {
|
|
3902
|
-
const
|
|
4013
|
+
const path19 = [];
|
|
3903
4014
|
let current = node;
|
|
3904
4015
|
while (current) {
|
|
3905
|
-
|
|
4016
|
+
path19.unshift(current);
|
|
3906
4017
|
current = parent.get(current);
|
|
3907
4018
|
}
|
|
3908
|
-
if (
|
|
3909
|
-
chains.push(
|
|
4019
|
+
if (path19.length >= 3) {
|
|
4020
|
+
chains.push(path19.slice(1).map((p) => `<- ${p}`).join(" "));
|
|
3910
4021
|
}
|
|
3911
4022
|
}
|
|
3912
4023
|
return chains.sort();
|
|
@@ -4327,131 +4438,12 @@ function registerFlowsCommand(program2) {
|
|
|
4327
4438
|
});
|
|
4328
4439
|
}
|
|
4329
4440
|
|
|
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
4441
|
// src/cli/preflight.ts
|
|
4448
4442
|
function registerPreflightCommand(program2) {
|
|
4449
|
-
program2.command("preflight").description("Unified diagnostic report:
|
|
4443
|
+
program2.command("preflight").description("Unified diagnostic report: drift, status, validation").option("--quick", "Skip drift detection for faster results").action(async (options) => {
|
|
4450
4444
|
try {
|
|
4451
4445
|
const cwd = process.cwd();
|
|
4452
4446
|
const graph = await loadGraph(cwd);
|
|
4453
|
-
const yggRoot = await findYggRoot(cwd);
|
|
4454
|
-
const journalEntries = await readJournal(yggRoot);
|
|
4455
4447
|
const driftedEntries = options.quick ? [] : (await detectDrift(graph)).entries.filter((e) => e.status !== "ok");
|
|
4456
4448
|
const nodeCount = graph.nodes.size;
|
|
4457
4449
|
const aspectCount = graph.aspects.length;
|
|
@@ -4466,16 +4458,6 @@ function registerPreflightCommand(program2) {
|
|
|
4466
4458
|
const lines = [];
|
|
4467
4459
|
lines.push("=== Preflight Report ===");
|
|
4468
4460
|
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
4461
|
if (options.quick) {
|
|
4480
4462
|
lines.push("Drift: skipped (--quick)");
|
|
4481
4463
|
} else if (driftedEntries.length === 0) {
|
|
@@ -4512,7 +4494,7 @@ function registerPreflightCommand(program2) {
|
|
|
4512
4494
|
}
|
|
4513
4495
|
lines.push("");
|
|
4514
4496
|
process.stdout.write(lines.join("\n"));
|
|
4515
|
-
const hasIssues =
|
|
4497
|
+
const hasIssues = !options.quick && driftedEntries.length > 0 || errors.length > 0;
|
|
4516
4498
|
process.exit(hasIssues ? 1 : 0);
|
|
4517
4499
|
} catch (error) {
|
|
4518
4500
|
process.stderr.write(`Error: ${error.message}
|
|
@@ -4543,9 +4525,6 @@ registerDepsCommand(program);
|
|
|
4543
4525
|
registerImpactCommand(program);
|
|
4544
4526
|
registerAspectsCommand(program);
|
|
4545
4527
|
registerFlowsCommand(program);
|
|
4546
|
-
registerJournalAddCommand(program);
|
|
4547
|
-
registerJournalReadCommand(program);
|
|
4548
|
-
registerJournalArchiveCommand(program);
|
|
4549
4528
|
registerPreflightCommand(program);
|
|
4550
4529
|
program.parse();
|
|
4551
4530
|
//# sourceMappingURL=bin.js.map
|