@azure-id/orc 1.7.1 → 1.8.2
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/CHANGELOG.md +3869 -3381
- package/README-id.md +942 -844
- package/README.md +882 -788
- package/bin/build-agents.js +43 -27
- package/bin/cli.js +45460 -44169
- package/bin/graph-extract.js +3216 -0
- package/bin/graph-gain.js +404 -0
- package/bin/graph-map.js +232 -0
- package/bin/graph-notes.js +229 -0
- package/bin/graph-query.js +1770 -0
- package/bin/graph-resolve.js +255 -0
- package/bin/graph-shard.js +325 -0
- package/bin/graph-signals.js +277 -0
- package/bin/graph.js +658 -0
- package/bin/verify-contracts.js +4809 -4553
- package/bin/verify-package.js +640 -616
- package/bin/webui/api.js +1425 -1414
- package/bin/webui/fixtures/index.js +584 -576
- package/bin/webui/fixtures/knowledge.js +356 -291
- package/bin/webui/fixtures/stats.js +107 -104
- package/bin/webui/i18n/en/knowledge.json +182 -151
- package/bin/webui/i18n/en/overview.json +101 -100
- package/bin/webui/i18n/id/knowledge.json +182 -151
- package/bin/webui/i18n/id/overview.json +101 -100
- package/bin/webui/js/panels/knowledge.js +1130 -1006
- package/bin/webui/js/panels/overview.js +492 -488
- package/package.json +39 -39
- package/templates/agents/MODEL-MAPPING.md +163 -158
- package/templates/agents/orc-executor-haiku-4-5.md +128 -121
- package/templates/agents/orc-executor-opus-4-7-high.md +129 -122
- package/templates/agents/orc-executor-opus-4-7-med.md +129 -122
- package/templates/agents/orc-executor-opus-4-8-high.md +129 -122
- package/templates/agents/orc-executor-opus-5-high.md +129 -122
- package/templates/agents/orc-executor-opus-5-low.md +129 -122
- package/templates/agents/orc-executor-opus-5-med.md +129 -122
- package/templates/agents/orc-executor-sonnet-4-6-high.md +129 -122
- package/templates/agents/orc-executor-sonnet-4-6-med.md +129 -122
- package/templates/agents/orc-executor-sonnet-5-high.md +129 -122
- package/templates/agents/orc-graph-noter-sonnet-4-6-med.md +89 -0
- package/templates/hooks/README.md +454 -396
- package/templates/hooks/orc-graph-hook.js +471 -0
- package/templates/hooks/orc-statusline-render.js +922 -921
- package/templates/hooks/orc-statusline.js +1596 -1545
- package/templates/skills/_shared/README.md +4 -0
- package/templates/skills/_shared/code-graph.md +348 -0
- package/templates/skills/_shared/opus5-only.md +4 -0
- package/templates/skills/_shared/phases/execution.md +168 -147
- package/templates/skills/_shared/phases/planning.md +149 -135
- package/templates/skills/_shared/phases/preflight.md +132 -118
- package/templates/skills/_shared/phases/review.md +63 -53
- package/templates/skills/_shared/phases/ship.md +100 -88
- package/templates/skills/_shared/phases/trace.md +8 -0
- package/templates/skills/_shared/phases/wiki-consult.md +194 -189
- package/templates/skills/_shared/read-ladder.md +132 -102
- package/templates/skills/_shared/return-validation.md +259 -250
- package/templates/skills/orc/SKILL.md +255 -254
- package/templates/skills/orc-analyze/SKILL.md +13 -13
- package/templates/skills/orc-diy/references/flow-schema.md +101 -100
- package/templates/skills/orc-fast/SKILL.md +236 -229
- package/templates/skills/orc-mini/SKILL.md +267 -259
- package/templates/skills/orc-quick/SKILL.md +378 -361
- package/templates/skills/orc-quick/references/dispatch-gate.md +6 -0
- package/templates/skills/orc-wiki/references/staleness.md +294 -288
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ── orc graph — NOTES (v1.8.0 W4) ───────────────────────────────────────────
|
|
3
|
+
//
|
|
4
|
+
// Layer 2: one sentence per function or method, written by
|
|
5
|
+
// `orc-graph-noter-sonnet-4-6-med`, stored by THIS module and nothing else.
|
|
6
|
+
//
|
|
7
|
+
// THE RULES THIS FILE HOLDS
|
|
8
|
+
//
|
|
9
|
+
// 1. A note is keyed by `(symbol, body_hash)`. It is shown as current ONLY
|
|
10
|
+
// while the symbol's body still hashes the same. A note about code that
|
|
11
|
+
// has since changed is labelled stale and never presented as a fact — the
|
|
12
|
+
// RepoMirage finding: agents trust provided context over the code.
|
|
13
|
+
// 2. The CLI is the only writer (S1). The agent pipes JSON to
|
|
14
|
+
// `orc graph notes apply -`; every row is validated against the CURRENT
|
|
15
|
+
// index, and a rejected row is named with its reason. The valid rows
|
|
16
|
+
// still apply.
|
|
17
|
+
// 3. Notes are for touched code only. `pending` needs `--files` — there is no
|
|
18
|
+
// repo-wide "note everything" mode, because that is the cost this layer
|
|
19
|
+
// must never become.
|
|
20
|
+
// 4. A batch below `--min` is not dispatched. The fixed start-up cost of a
|
|
21
|
+
// subagent (~5.7K tokens) makes a one-symbol batch the most expensive note
|
|
22
|
+
// there is. Nothing is lost by waiting: pending is recomputed from hashes.
|
|
23
|
+
// 5. `notes.jsonl` is append-only between compactions. `orc graph gc`
|
|
24
|
+
// compacts it to the latest note per symbol whose hash is still current.
|
|
25
|
+
|
|
26
|
+
const fs = require("fs");
|
|
27
|
+
|
|
28
|
+
const NOTE_MAX = 240;
|
|
29
|
+
// A two-line getter gains nothing from a sentence about it.
|
|
30
|
+
const MIN_LINES = 3;
|
|
31
|
+
// Classes are excluded: a class body hash moves whenever ANY of its methods
|
|
32
|
+
// moves, so a class note would go stale on every edit to the class.
|
|
33
|
+
// A route handler is a function with no name — it is noted like one.
|
|
34
|
+
const NOTE_KINDS = new Set(["function", "method", "route"]);
|
|
35
|
+
|
|
36
|
+
const G = () => require("./graph.js");
|
|
37
|
+
|
|
38
|
+
function clampInt(v, lo, hi, def) {
|
|
39
|
+
const n = Number(v);
|
|
40
|
+
if (!Number.isFinite(n) || v === true || v === undefined || v === null || v === "") return def;
|
|
41
|
+
return Math.max(lo, Math.min(hi, Math.round(n)));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function readIndex(claudeDir) {
|
|
45
|
+
try {
|
|
46
|
+
return JSON.parse(fs.readFileSync(G().graphPaths(claudeDir).index, "utf8"));
|
|
47
|
+
} catch (_) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function readNotes(file) {
|
|
53
|
+
const rows = [];
|
|
54
|
+
let text;
|
|
55
|
+
try {
|
|
56
|
+
text = fs.readFileSync(file, "utf8");
|
|
57
|
+
} catch (_) {
|
|
58
|
+
return rows;
|
|
59
|
+
}
|
|
60
|
+
for (const line of text.split("\n")) {
|
|
61
|
+
if (!line.trim()) continue;
|
|
62
|
+
try {
|
|
63
|
+
const r = JSON.parse(line);
|
|
64
|
+
if (r && typeof r.sym === "string" && typeof r.body_hash === "string" && typeof r.note === "string") rows.push(r);
|
|
65
|
+
} catch (_) {
|
|
66
|
+
// A torn last line from a crash mid-append is skipped, never fatal.
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return rows;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// The latest note per (sym, body_hash), and the latest note per sym whatever
|
|
73
|
+
// its hash (used only to say "stale").
|
|
74
|
+
function noteIndex(claudeDir) {
|
|
75
|
+
const rows = readNotes(G().graphPaths(claudeDir).notes);
|
|
76
|
+
const byKey = new Map();
|
|
77
|
+
const bySym = new Map();
|
|
78
|
+
for (const r of rows) {
|
|
79
|
+
byKey.set(`${r.sym}\0${r.body_hash}`, r);
|
|
80
|
+
bySym.set(r.sym, r);
|
|
81
|
+
}
|
|
82
|
+
return { byKey, bySym, rows: rows.length };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// The ONE resolution rule, shared by every reader:
|
|
86
|
+
//
|
|
87
|
+
// 1. a CURRENT model note — somebody paid for it and it still describes
|
|
88
|
+
// this body
|
|
89
|
+
// 2. the DOC note (N1) — the author's own first sentence, re-extracted
|
|
90
|
+
// with the body, so it is never stale on its own
|
|
91
|
+
// 3. a STALE model note — reported as stale, never repeated as a fact
|
|
92
|
+
//
|
|
93
|
+
// A doc outranks a stale note because a sentence that matches the current
|
|
94
|
+
// bytes beats one that matched a body nobody has any more. It does NOT
|
|
95
|
+
// outrank a current model note: a comment can lie, and the model read the code.
|
|
96
|
+
// That is why the shared precedence line reads `graph notes and doc notes` as
|
|
97
|
+
// ONE rung, below a stale wiki — this function is the code half of it.
|
|
98
|
+
function noteFor(idx, sym) {
|
|
99
|
+
if (!sym) return null;
|
|
100
|
+
const cur = idx && idx.byKey.get(`${sym.id}\0${sym.body_hash}`);
|
|
101
|
+
if (cur) return { text: cur.note, model: cur.model, at: cur.at, current: true, source: "model" };
|
|
102
|
+
if (sym.doc) return { text: sym.doc, model: "parser", at: null, current: true, source: "doc" };
|
|
103
|
+
const any = idx && idx.bySym.get(sym.id);
|
|
104
|
+
if (any) return { text: null, model: any.model, at: any.at, current: false, source: "model" };
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const normPath = (f) => String(f).replace(/\\/g, "/").replace(/^\.\//, "");
|
|
109
|
+
|
|
110
|
+
// exit 0 = rows to note · 1 = no index / no --files · 5 = none, or fewer than --min
|
|
111
|
+
function notesPending(claudeDir, opts, root) {
|
|
112
|
+
const index = readIndex(claudeDir);
|
|
113
|
+
if (!index || !index.by_file) return { ok: false, state: "none", reason: "no-index", exit: 1 };
|
|
114
|
+
const files = [...new Set((opts.files || []).map(normPath).filter(Boolean))];
|
|
115
|
+
if (!files.length) return { ok: false, state: "usage", reason: "missing-files", exit: 1 };
|
|
116
|
+
const cap = clampInt(opts.cap, 1, 200, 40);
|
|
117
|
+
const min = clampInt(opts.min, 1, 200, 5);
|
|
118
|
+
const idx = noteIndex(claudeDir);
|
|
119
|
+
const missing = [];
|
|
120
|
+
const rows = [];
|
|
121
|
+
let documented = 0;
|
|
122
|
+
for (const rel of files) {
|
|
123
|
+
const f = index.by_file[rel];
|
|
124
|
+
if (!f) {
|
|
125
|
+
missing.push(rel);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
for (const s of f.symbols || []) {
|
|
129
|
+
if (!NOTE_KINDS.has(s.kind)) continue;
|
|
130
|
+
if (s.lines[1] - s.lines[0] + 1 < MIN_LINES) continue;
|
|
131
|
+
if (idx.byKey.has(`${s.id}\0${s.body_hash}`)) continue;
|
|
132
|
+
// N1 (v1.8.2 W4): the author already wrote the sentence. Paying a
|
|
133
|
+
// subagent to write it again is the one cost this layer must not have.
|
|
134
|
+
if (s.doc) {
|
|
135
|
+
documented++;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
rows.push({ sym: s.id, file: rel, lines: s.lines, body_hash: s.body_hash, restale: idx.bySym.has(s.id) });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const base = { ok: true, files, missing, total: rows.length, documented, cap, min };
|
|
142
|
+
if (!rows.length) return { ...base, state: "none", rows: [], more: 0, exit: 5 };
|
|
143
|
+
if (rows.length < min) return { ...base, state: "below-min", rows: [], waiting: rows.map((r) => r.sym), more: 0, exit: 5 };
|
|
144
|
+
const out = rows.slice(0, cap);
|
|
145
|
+
// N2 (v1.8.2 W4) — `--with-source`: the ranges, in the SAME call. The noter
|
|
146
|
+
// used to open every symbol with a `Read`, so a 40-symbol batch was 40 round
|
|
147
|
+
// trips before it wrote a word. One call, `SRC_PER_ROW` lines each, and the
|
|
148
|
+
// noter reads nothing.
|
|
149
|
+
if (opts.withSource) for (const r of out) r.source = readRange(root, r.file, r.lines);
|
|
150
|
+
return { ...base, state: "pending", rows: out, more: rows.length - out.length, with_source: !!opts.withSource, exit: 0 };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const SRC_PER_ROW = 120;
|
|
154
|
+
|
|
155
|
+
function readRange(root, rel, lines) {
|
|
156
|
+
if (!root) return null;
|
|
157
|
+
try {
|
|
158
|
+
const all = require("fs").readFileSync(require("path").join(root, ...rel.split("/")), "utf8").split(/\r?\n/);
|
|
159
|
+
const from = Math.max(1, lines[0]);
|
|
160
|
+
const to = Math.min(all.length, Math.min(lines[1], from + SRC_PER_ROW - 1));
|
|
161
|
+
if (to < from) return null;
|
|
162
|
+
return { from, to, cut: Math.max(0, lines[1] - to), text: all.slice(from - 1, to).join("\n") };
|
|
163
|
+
} catch (_) {
|
|
164
|
+
return null; // a file that moved between the index and now: the noter reads it
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// exit 0 = every row applied · 1 = no index / locked · 6 = one or more rows rejected
|
|
169
|
+
function notesApply(claudeDir, payload) {
|
|
170
|
+
const index = readIndex(claudeDir);
|
|
171
|
+
if (!index || !index.by_file) return { ok: false, state: "none", reason: "no-index", exit: 1 };
|
|
172
|
+
let data = payload;
|
|
173
|
+
if (typeof payload === "string") {
|
|
174
|
+
try {
|
|
175
|
+
data = JSON.parse(payload);
|
|
176
|
+
} catch (_) {
|
|
177
|
+
return { ok: false, state: "invalid", reason: "not-json", applied: 0, rejected: [], exit: 6 };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const list = Array.isArray(data) ? data : data && Array.isArray(data.notes) ? data.notes : null;
|
|
181
|
+
if (!list) return { ok: false, state: "invalid", reason: "no-notes-array", applied: 0, rejected: [], exit: 6 };
|
|
182
|
+
const model = data && !Array.isArray(data) && typeof data.model === "string" && data.model.trim() ? data.model.trim().slice(0, 60) : "unknown";
|
|
183
|
+
|
|
184
|
+
const syms = new Map();
|
|
185
|
+
for (const f of Object.values(index.by_file)) for (const s of f.symbols || []) syms.set(s.id, s);
|
|
186
|
+
const applied = [];
|
|
187
|
+
const rejected = [];
|
|
188
|
+
const at = G().stamp(new Date());
|
|
189
|
+
list.forEach((n, i) => {
|
|
190
|
+
const reject = (reason) => rejected.push({ index: i, sym: (n && n.sym) || null, reason });
|
|
191
|
+
if (!n || typeof n !== "object") return reject("not-an-object");
|
|
192
|
+
const s = syms.get(n.sym);
|
|
193
|
+
if (!s) return reject("unknown-symbol");
|
|
194
|
+
if (n.body_hash !== s.body_hash) return reject("stale-body-hash");
|
|
195
|
+
if (typeof n.note !== "string" || !n.note.trim()) return reject("empty-note");
|
|
196
|
+
const text = n.note.trim();
|
|
197
|
+
if (/[\r\n]/.test(text)) return reject("multi-line");
|
|
198
|
+
if (text.length > NOTE_MAX) return reject("too-long");
|
|
199
|
+
applied.push({ sym: s.id, body_hash: s.body_hash, note: text, model, at });
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
if (applied.length) {
|
|
203
|
+
const g = G();
|
|
204
|
+
const p = g.graphPaths(claudeDir);
|
|
205
|
+
const lock = g.acquireLock(p);
|
|
206
|
+
if (!lock.ok) return { ok: false, state: "unavailable", reason: "locked", holder: lock.holder, applied: 0, rejected, exit: 1 };
|
|
207
|
+
try {
|
|
208
|
+
fs.appendFileSync(p.notes, applied.map((r) => JSON.stringify(r)).join("\n") + "\n");
|
|
209
|
+
} finally {
|
|
210
|
+
g.releaseLock(p);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return { ok: true, state: rejected.length ? "partial" : "applied", applied: applied.length, rejected, model, exit: rejected.length ? 6 : 0 };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Called by `graphGc` while it already holds the lock.
|
|
217
|
+
function compactNotes(p, index, atomicWrite) {
|
|
218
|
+
const rows = readNotes(p.notes);
|
|
219
|
+
if (!rows.length) return { kept: 0, removed: 0 };
|
|
220
|
+
const current = new Map();
|
|
221
|
+
for (const f of Object.values((index && index.by_file) || {})) for (const s of f.symbols || []) current.set(s.id, s.body_hash);
|
|
222
|
+
const latest = new Map();
|
|
223
|
+
for (const r of rows) if (current.get(r.sym) === r.body_hash) latest.set(r.sym, r);
|
|
224
|
+
const keep = [...latest.values()];
|
|
225
|
+
if (keep.length !== rows.length) atomicWrite(p.notes, keep.map((r) => JSON.stringify(r)).join("\n") + (keep.length ? "\n" : ""));
|
|
226
|
+
return { kept: keep.length, removed: rows.length - keep.length };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
module.exports = { NOTE_MAX, notesPending, notesApply, noteIndex, noteFor, compactNotes };
|