@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 +1 -7
- package/dist/bin.js +180 -195
- package/dist/bin.js.map +1 -1
- package/dist/templates/rules.ts +52 -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,30 @@ 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:
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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\` + \`
|
|
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
|
|
165
|
-
- [ ] 3. If
|
|
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.
|
|
178
|
-
- [ ] 2. yg
|
|
179
|
-
- [ ] 3.
|
|
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
|
|
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
|
|
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:
|
|
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 =
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
3068
|
+
state2[key] = value;
|
|
2964
3069
|
}
|
|
2965
3070
|
}
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
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
|
-
|
|
2972
|
-
const
|
|
2973
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
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
|
|
3629
|
-
const node = graph.nodes.get(
|
|
3745
|
+
const path19 = options.root.trim().replace(/\/$/, "");
|
|
3746
|
+
const node = graph.nodes.get(path19);
|
|
3630
3747
|
if (!node) {
|
|
3631
|
-
process.stderr.write(`Error: path '${
|
|
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
|
|
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
|
|
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
|
|
4019
|
+
const path19 = [];
|
|
3903
4020
|
let current = node;
|
|
3904
4021
|
while (current) {
|
|
3905
|
-
|
|
4022
|
+
path19.unshift(current);
|
|
3906
4023
|
current = parent.get(current);
|
|
3907
4024
|
}
|
|
3908
|
-
if (
|
|
3909
|
-
chains.push(
|
|
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:
|
|
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 =
|
|
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
|