@nanonets/graft 0.10.0 → 0.12.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 +34 -13
- package/dist/ai/crux.d.ts.map +1 -1
- package/dist/ai/crux.js +7 -6
- package/dist/ai/crux.js.map +1 -1
- package/dist/ai/failure.d.ts +49 -0
- package/dist/ai/failure.d.ts.map +1 -0
- package/dist/ai/failure.js +74 -0
- package/dist/ai/failure.js.map +1 -0
- package/dist/ai/llm/anthropic.d.ts.map +1 -1
- package/dist/ai/llm/anthropic.js +4 -1
- package/dist/ai/llm/anthropic.js.map +1 -1
- package/dist/ai/llm/claude-cli.d.ts +60 -0
- package/dist/ai/llm/claude-cli.d.ts.map +1 -0
- package/dist/ai/llm/claude-cli.js +417 -0
- package/dist/ai/llm/claude-cli.js.map +1 -0
- package/dist/ai/llm/openai.d.ts.map +1 -1
- package/dist/ai/llm/openai.js +2 -0
- package/dist/ai/llm/openai.js.map +1 -1
- package/dist/ai/llm/types.d.ts +9 -0
- package/dist/ai/llm/types.d.ts.map +1 -1
- package/dist/ai/llm/types.js +12 -1
- package/dist/ai/llm/types.js.map +1 -1
- package/dist/ask/ask.d.ts.map +1 -1
- package/dist/ask/ask.js +39 -45
- package/dist/ask/ask.js.map +1 -1
- package/dist/ask/fuse.d.ts +83 -46
- package/dist/ask/fuse.d.ts.map +1 -1
- package/dist/ask/fuse.js +165 -67
- package/dist/ask/fuse.js.map +1 -1
- package/dist/blast/blast-cli.d.ts +30 -0
- package/dist/blast/blast-cli.d.ts.map +1 -0
- package/dist/blast/blast-cli.js +163 -0
- package/dist/blast/blast-cli.js.map +1 -0
- package/dist/blast/blast.d.ts +119 -0
- package/dist/blast/blast.d.ts.map +1 -0
- package/dist/blast/blast.js +383 -0
- package/dist/blast/blast.js.map +1 -0
- package/dist/blast/diff.d.ts +53 -0
- package/dist/blast/diff.d.ts.map +1 -0
- package/dist/blast/diff.js +193 -0
- package/dist/blast/diff.js.map +1 -0
- package/dist/blast/modules.d.ts +33 -0
- package/dist/blast/modules.d.ts.map +1 -0
- package/dist/blast/modules.js +83 -0
- package/dist/blast/modules.js.map +1 -0
- package/dist/blast/name.d.ts +61 -0
- package/dist/blast/name.d.ts.map +1 -0
- package/dist/blast/name.js +233 -0
- package/dist/blast/name.js.map +1 -0
- package/dist/blast/render.d.ts +37 -0
- package/dist/blast/render.d.ts.map +1 -0
- package/dist/blast/render.js +300 -0
- package/dist/blast/render.js.map +1 -0
- package/dist/blast/viz.d.ts +17 -0
- package/dist/blast/viz.d.ts.map +1 -0
- package/dist/blast/viz.js +324 -0
- package/dist/blast/viz.js.map +1 -0
- package/dist/claude/hooks.d.ts +13 -0
- package/dist/claude/hooks.d.ts.map +1 -1
- package/dist/claude/hooks.js +44 -4
- package/dist/claude/hooks.js.map +1 -1
- package/dist/claude/shim-template.d.ts.map +1 -1
- package/dist/claude/shim-template.js +45 -15
- package/dist/claude/shim-template.js.map +1 -1
- package/dist/claude/state.d.ts +5 -0
- package/dist/claude/state.d.ts.map +1 -1
- package/dist/claude/state.js.map +1 -1
- package/dist/cli-picker.d.ts +44 -5
- package/dist/cli-picker.d.ts.map +1 -1
- package/dist/cli-picker.js +73 -26
- package/dist/cli-picker.js.map +1 -1
- package/dist/cli.js +270 -14
- package/dist/cli.js.map +1 -1
- package/dist/context/build.d.ts +8 -0
- package/dist/context/build.d.ts.map +1 -1
- package/dist/context/build.js +57 -9
- package/dist/context/build.js.map +1 -1
- package/dist/context/check.d.ts.map +1 -1
- package/dist/context/check.js +7 -6
- package/dist/context/check.js.map +1 -1
- package/dist/context/node-file.d.ts.map +1 -1
- package/dist/context/node-file.js +9 -2
- package/dist/context/node-file.js.map +1 -1
- package/dist/graph/bindings.d.ts.map +1 -1
- package/dist/graph/bindings.js +245 -2
- package/dist/graph/bindings.js.map +1 -1
- package/dist/graph/build.d.ts.map +1 -1
- package/dist/graph/build.js +36 -16
- package/dist/graph/build.js.map +1 -1
- package/dist/graph/check.d.ts +4 -0
- package/dist/graph/check.d.ts.map +1 -1
- package/dist/graph/check.js +8 -1
- package/dist/graph/check.js.map +1 -1
- package/dist/graph/container.d.ts +63 -0
- package/dist/graph/container.d.ts.map +1 -0
- package/dist/graph/container.js +185 -0
- package/dist/graph/container.js.map +1 -0
- package/dist/graph/enrich.d.ts +18 -0
- package/dist/graph/enrich.d.ts.map +1 -1
- package/dist/graph/enrich.js +55 -2
- package/dist/graph/enrich.js.map +1 -1
- package/dist/graph/extract.d.ts +8 -1
- package/dist/graph/extract.d.ts.map +1 -1
- package/dist/graph/extract.js +401 -12
- package/dist/graph/extract.js.map +1 -1
- package/dist/graph/generic.d.ts +34 -1
- package/dist/graph/generic.d.ts.map +1 -1
- package/dist/graph/generic.js +176 -6
- package/dist/graph/generic.js.map +1 -1
- package/dist/graph/invariants.d.ts +32 -0
- package/dist/graph/invariants.d.ts.map +1 -0
- package/dist/graph/invariants.js +49 -0
- package/dist/graph/invariants.js.map +1 -0
- package/dist/graph/queries/clojure.scm +58 -0
- package/dist/graph/resolve.d.ts.map +1 -1
- package/dist/graph/resolve.js +249 -23
- package/dist/graph/resolve.js.map +1 -1
- package/dist/graph/scopes.d.ts +2 -2
- package/dist/graph/scopes.d.ts.map +1 -1
- package/dist/graph/scopes.js +8 -6
- package/dist/graph/scopes.js.map +1 -1
- package/dist/graph/source-files.d.ts +13 -4
- package/dist/graph/source-files.d.ts.map +1 -1
- package/dist/graph/source-files.js +35 -12
- package/dist/graph/source-files.js.map +1 -1
- package/dist/graph/types.d.ts +3 -1
- package/dist/graph/types.d.ts.map +1 -1
- package/dist/graph/workspace-cli.d.ts +2 -0
- package/dist/graph/workspace-cli.d.ts.map +1 -1
- package/dist/graph/workspace-cli.js +10 -4
- package/dist/graph/workspace-cli.js.map +1 -1
- package/dist/graph/workspace.d.ts +11 -6
- package/dist/graph/workspace.d.ts.map +1 -1
- package/dist/graph/workspace.js +24 -13
- package/dist/graph/workspace.js.map +1 -1
- package/dist/graph/write.d.ts.map +1 -1
- package/dist/graph/write.js +25 -2
- package/dist/graph/write.js.map +1 -1
- package/dist/hosts/antigravity.d.ts +7 -0
- package/dist/hosts/antigravity.d.ts.map +1 -0
- package/dist/hosts/antigravity.js +42 -0
- package/dist/hosts/antigravity.js.map +1 -0
- package/dist/hosts/codex-hooks.d.ts +3 -3
- package/dist/hosts/codex-hooks.d.ts.map +1 -1
- package/dist/hosts/codex-hooks.js +27 -21
- package/dist/hosts/codex-hooks.js.map +1 -1
- package/dist/hosts/init.d.ts.map +1 -1
- package/dist/hosts/init.js +6 -1
- package/dist/hosts/init.js.map +1 -1
- package/dist/hosts/mcp-config.d.ts.map +1 -1
- package/dist/hosts/mcp-config.js +7 -0
- package/dist/hosts/mcp-config.js.map +1 -1
- package/dist/hosts/plan.d.ts +1 -1
- package/dist/hosts/plan.d.ts.map +1 -1
- package/dist/hosts/plan.js +2 -0
- package/dist/hosts/plan.js.map +1 -1
- package/dist/hosts/registry.d.ts.map +1 -1
- package/dist/hosts/registry.js +13 -0
- package/dist/hosts/registry.js.map +1 -1
- package/dist/ingest/fs.d.ts +9 -2
- package/dist/ingest/fs.d.ts.map +1 -1
- package/dist/ingest/fs.js +100 -16
- package/dist/ingest/fs.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +45 -2
- package/dist/mcp/server.js.map +1 -1
- package/dist/telemetry/contract.d.ts +90 -0
- package/dist/telemetry/contract.d.ts.map +1 -0
- package/dist/telemetry/contract.js +203 -0
- package/dist/telemetry/contract.js.map +1 -0
- package/dist/telemetry/flush.d.ts +21 -0
- package/dist/telemetry/flush.d.ts.map +1 -0
- package/dist/telemetry/flush.js +84 -0
- package/dist/telemetry/flush.js.map +1 -0
- package/dist/telemetry/gate.d.ts +16 -0
- package/dist/telemetry/gate.d.ts.map +1 -0
- package/dist/telemetry/gate.js +64 -0
- package/dist/telemetry/gate.js.map +1 -0
- package/dist/telemetry/identity.d.ts +33 -0
- package/dist/telemetry/identity.d.ts.map +1 -0
- package/dist/telemetry/identity.js +80 -0
- package/dist/telemetry/identity.js.map +1 -0
- package/dist/telemetry/index.d.ts +18 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +16 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/telemetry/key.d.ts +21 -0
- package/dist/telemetry/key.d.ts.map +1 -0
- package/dist/telemetry/key.js +49 -0
- package/dist/telemetry/key.js.map +1 -0
- package/dist/telemetry/notice.d.ts +19 -0
- package/dist/telemetry/notice.d.ts.map +1 -0
- package/dist/telemetry/notice.js +90 -0
- package/dist/telemetry/notice.js.map +1 -0
- package/dist/telemetry/queue.d.ts +35 -0
- package/dist/telemetry/queue.d.ts.map +1 -0
- package/dist/telemetry/queue.js +162 -0
- package/dist/telemetry/queue.js.map +1 -0
- package/dist/telemetry/send.d.ts +19 -0
- package/dist/telemetry/send.d.ts.map +1 -0
- package/dist/telemetry/send.js +84 -0
- package/dist/telemetry/send.js.map +1 -0
- package/dist/telemetry/sessions.d.ts +8 -0
- package/dist/telemetry/sessions.d.ts.map +1 -0
- package/dist/telemetry/sessions.js +81 -0
- package/dist/telemetry/sessions.js.map +1 -0
- package/dist/telemetry/track.d.ts +43 -0
- package/dist/telemetry/track.d.ts.map +1 -0
- package/dist/telemetry/track.js +108 -0
- package/dist/telemetry/track.js.map +1 -0
- package/dist/upkeep-run.d.ts +15 -0
- package/dist/upkeep-run.d.ts.map +1 -0
- package/dist/upkeep-run.js +60 -0
- package/dist/upkeep-run.js.map +1 -0
- package/dist/upkeep.d.ts +114 -0
- package/dist/upkeep.d.ts.map +1 -0
- package/dist/upkeep.js +232 -0
- package/dist/upkeep.js.map +1 -0
- package/dist/util/num.d.ts +3 -0
- package/dist/util/num.d.ts.map +1 -0
- package/dist/util/num.js +20 -0
- package/dist/util/num.js.map +1 -0
- package/dist/util/state.d.ts +11 -4
- package/dist/util/state.d.ts.map +1 -1
- package/dist/util/state.js +9 -0
- package/dist/util/state.js.map +1 -1
- package/dist/viewer/app.js +1 -1
- package/dist/viewer/index.html +1 -0
- package/dist/viewer/style.css +23 -2
- package/dist/viz/assemble.d.ts +33 -0
- package/dist/viz/assemble.d.ts.map +1 -1
- package/dist/viz/assemble.js.map +1 -1
- package/dist/viz/export.d.ts +39 -0
- package/dist/viz/export.d.ts.map +1 -0
- package/dist/viz/export.js +98 -0
- package/dist/viz/export.js.map +1 -0
- package/package.json +15 -5
- package/scripts/stamp-telemetry-key.mjs +53 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `track()` — the only way an event is ever created, and the place the contract
|
|
3
|
+
* is enforced.
|
|
4
|
+
*
|
|
5
|
+
* Call sites pass a name and some properties; this drops anything the contract
|
|
6
|
+
* (contract.ts) does not list, stamps the common properties, and appends to the
|
|
7
|
+
* local queue. It never sends, never blocks, and never throws — a telemetry bug
|
|
8
|
+
* must be incapable of failing a build or a query.
|
|
9
|
+
*
|
|
10
|
+
* Two rules do the actual privacy work, and both are enforced here rather than
|
|
11
|
+
* asked of callers:
|
|
12
|
+
*
|
|
13
|
+
* • unknown event → dropped whole; unknown property key → dropped, the rest of
|
|
14
|
+
* the event survives. A careless `track('query', { path })` sends `query`
|
|
15
|
+
* without a path.
|
|
16
|
+
* • non-string values → dropped. Everything that crosses the wire is a bucket
|
|
17
|
+
* label or an enum member, so a raw count or a raw message cannot get out
|
|
18
|
+
* even under a key that happens to be allowed.
|
|
19
|
+
*/
|
|
20
|
+
import { EVENTS } from './contract.js';
|
|
21
|
+
import { enqueue } from './queue.js';
|
|
22
|
+
import { telemetryOn } from './gate.js';
|
|
23
|
+
import { installId, repoId } from './identity.js';
|
|
24
|
+
import { runningVersion } from '../upkeep.js';
|
|
25
|
+
/**
|
|
26
|
+
* Which agent graft is running under, from the surface plus one env probe.
|
|
27
|
+
*
|
|
28
|
+
* Only the PRESENCE of `CLAUDECODE` is read, never its value — the distinction
|
|
29
|
+
* matters: a value could be anything a user's shell profile put there, presence
|
|
30
|
+
* is a boolean. Anything unrecognised is `cli`, which is the honest answer.
|
|
31
|
+
*/
|
|
32
|
+
export function detectHost(explicit, env = process.env) {
|
|
33
|
+
if (explicit)
|
|
34
|
+
return explicit;
|
|
35
|
+
if (env.CLAUDECODE !== undefined)
|
|
36
|
+
return 'claude-code';
|
|
37
|
+
return 'cli';
|
|
38
|
+
}
|
|
39
|
+
/** The properties every event carries. Kept in one place so no call site can
|
|
40
|
+
* disagree about them. */
|
|
41
|
+
function commonProps(ctx) {
|
|
42
|
+
const props = {
|
|
43
|
+
app_version: runningVersion(),
|
|
44
|
+
os: process.platform,
|
|
45
|
+
arch: process.arch,
|
|
46
|
+
node_major: String(process.versions.node.split('.')[0]),
|
|
47
|
+
ci: 'false', // an event only exists at all when the CI gate passed
|
|
48
|
+
agent_host: detectHost(ctx.host, ctx.env ?? process.env),
|
|
49
|
+
};
|
|
50
|
+
if (ctx.repo) {
|
|
51
|
+
try {
|
|
52
|
+
props.repo_id = repoId(ctx.repo);
|
|
53
|
+
}
|
|
54
|
+
catch { /* unwritable cache — omit */ }
|
|
55
|
+
}
|
|
56
|
+
return props;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Record one event, if the contract allows it and the gates are open.
|
|
60
|
+
*
|
|
61
|
+
* Returns the event that was queued, or null — the return value exists for
|
|
62
|
+
* tests and for `graft telemetry debug`, not for control flow at call sites.
|
|
63
|
+
*/
|
|
64
|
+
export function track(event, props = {}, ctx = {}) {
|
|
65
|
+
try {
|
|
66
|
+
if (!telemetryOn(ctx.home, ctx.env))
|
|
67
|
+
return null;
|
|
68
|
+
// `Object.hasOwn`, not `EVENTS[event]`: a bare object literal inherits from
|
|
69
|
+
// Object.prototype, so `EVENTS['constructor']` is truthy and would sail past
|
|
70
|
+
// a plain existence check — only failing later, on a TypeError swallowed by
|
|
71
|
+
// the catch below. The allowlist must reject by design, not by exception.
|
|
72
|
+
if (!Object.hasOwn(EVENTS, event))
|
|
73
|
+
return null;
|
|
74
|
+
const allowed = EVENTS[event];
|
|
75
|
+
const properties = commonProps(ctx);
|
|
76
|
+
for (const [k, v] of Object.entries(props)) {
|
|
77
|
+
if (allowed.has(k) && typeof v === 'string')
|
|
78
|
+
properties[k] = v;
|
|
79
|
+
}
|
|
80
|
+
const queued = {
|
|
81
|
+
event,
|
|
82
|
+
properties,
|
|
83
|
+
timestamp: new Date().toISOString(),
|
|
84
|
+
distinct_id: installId(ctx.home).id,
|
|
85
|
+
};
|
|
86
|
+
enqueue(queued, ctx.home);
|
|
87
|
+
return queued;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return null; // telemetry never surfaces as a user-visible failure
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The `first_run` event, fired the once. Separate from `track` because it is the
|
|
95
|
+
* only event whose trigger is "the id did not exist a moment ago", which only
|
|
96
|
+
* `installId()` can know.
|
|
97
|
+
*/
|
|
98
|
+
export function trackFirstRunIfNew(ctx = {}) {
|
|
99
|
+
try {
|
|
100
|
+
if (!telemetryOn(ctx.home, ctx.env))
|
|
101
|
+
return;
|
|
102
|
+
if (!installId(ctx.home).firstRun)
|
|
103
|
+
return;
|
|
104
|
+
track('first_run', {}, ctx);
|
|
105
|
+
}
|
|
106
|
+
catch { /* never */ }
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=track.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"track.js","sourceRoot":"","sources":["../../src/telemetry/track.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface UpkeepResult {
|
|
2
|
+
/** Lines worth showing the agent/user, already formatted. Usually empty. */
|
|
3
|
+
lines: string[];
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* @param repo the project dir
|
|
7
|
+
* @param current the running graft's version
|
|
8
|
+
* @param opts.background false in hook contexts: read the update cache, never
|
|
9
|
+
* spawn a fetch. Hooks run under a hard timeout and must stay off the network.
|
|
10
|
+
*/
|
|
11
|
+
export declare function runUpkeep(repo: string, current: string, opts?: {
|
|
12
|
+
background?: boolean;
|
|
13
|
+
home?: string;
|
|
14
|
+
}): UpkeepResult;
|
|
15
|
+
//# sourceMappingURL=upkeep-run.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upkeep-run.d.ts","sourceRoot":"","sources":["../src/upkeep-run.ts"],"names":[],"mappings":"AAmBA,MAAM,WAAW,YAAY;IAC3B,4EAA4E;IAC5E,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAyBD;;;;;GAKG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,IAAI,GAAE;IAAE,UAAU,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GACjD,YAAY,CAad"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The wired-up version of `./upkeep.ts` — one call every entry point makes.
|
|
3
|
+
*
|
|
4
|
+
* Split from `upkeep.ts` so that module stays pure and unit-testable (no init
|
|
5
|
+
* writes, no spawning): this file is the only place that knows how to actually
|
|
6
|
+
* re-run the wiring, and it is deliberately thin.
|
|
7
|
+
*/
|
|
8
|
+
import { runInit } from './claude/init.js';
|
|
9
|
+
import { runHostsInit } from './hosts/init.js';
|
|
10
|
+
import { graftCliPath } from './claude/paths.js';
|
|
11
|
+
import { formatUpdateNudge, formatWiringRefresh, maybeRefreshInBackground, readUpdateCache, reconcileWiring, } from './upkeep.js';
|
|
12
|
+
/**
|
|
13
|
+
* Re-write the wiring for exactly the hosts a previous `init` chose here, with
|
|
14
|
+
* exactly the flags it was given.
|
|
15
|
+
*
|
|
16
|
+
* `build: false` matters: this runs at session start and at MCP boot, and a graph
|
|
17
|
+
* build there would stall the agent's first turn.
|
|
18
|
+
*
|
|
19
|
+
* The out-of-repo writes (`~/.codex/hooks.json`, `~/.codex/config.toml`) ARE
|
|
20
|
+
* included, because nothing else would ever refresh them: no skill, rule file, or
|
|
21
|
+
* MCP instruction tells an agent to run `graft init`, so leaving them out means a
|
|
22
|
+
* Codex user upgrades the binary and keeps the old hook config forever. They're
|
|
23
|
+
* safe to replay — `installCodexHooks` no-ops when `~/.codex` is absent, rewrites
|
|
24
|
+
* only its own entry (matched on `graft-hooks.cjs`), and reports `unchanged` when
|
|
25
|
+
* the bytes match. A user who declined them at init time is honoured via
|
|
26
|
+
* `opts.global`/`opts.hooks`, replayed from the stamp.
|
|
27
|
+
*/
|
|
28
|
+
function rewriteWiring(repo, hosts, opts) {
|
|
29
|
+
if (hosts.includes('claude'))
|
|
30
|
+
runInit(repo, { build: false, cliPath: graftCliPath() });
|
|
31
|
+
const others = hosts.filter((h) => h !== 'claude');
|
|
32
|
+
if (others.length)
|
|
33
|
+
runHostsInit(repo, { agents: others, global: opts.global, mcp: opts.mcp, hooks: opts.hooks });
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* @param repo the project dir
|
|
37
|
+
* @param current the running graft's version
|
|
38
|
+
* @param opts.background false in hook contexts: read the update cache, never
|
|
39
|
+
* spawn a fetch. Hooks run under a hard timeout and must stay off the network.
|
|
40
|
+
*/
|
|
41
|
+
export function runUpkeep(repo, current, opts = {}) {
|
|
42
|
+
const lines = [];
|
|
43
|
+
try {
|
|
44
|
+
const refreshed = reconcileWiring(repo, current, { rewrite: rewriteWiring });
|
|
45
|
+
const refreshLine = formatWiringRefresh(refreshed);
|
|
46
|
+
if (refreshLine)
|
|
47
|
+
lines.push(refreshLine);
|
|
48
|
+
}
|
|
49
|
+
catch { /* fail-soft: wiring refresh is never worth breaking a session for */ }
|
|
50
|
+
try {
|
|
51
|
+
if (opts.background !== false)
|
|
52
|
+
maybeRefreshInBackground(opts.home);
|
|
53
|
+
const nudge = formatUpdateNudge(current, readUpdateCache(opts.home)?.latest);
|
|
54
|
+
if (nudge)
|
|
55
|
+
lines.push(nudge);
|
|
56
|
+
}
|
|
57
|
+
catch { /* same */ }
|
|
58
|
+
return { lines };
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=upkeep-run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upkeep-run.js","sourceRoot":"","sources":["../src/upkeep-run.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,wBAAwB,EACxB,eAAe,EACf,eAAe,GAEhB,MAAM,aAAa,CAAC;AAOrB;;;;;;;;;;;;;;;GAeG;AACH,SAAS,aAAa,CAAC,IAAY,EAAE,KAAe,EAAE,IAAgB;IACpE,IAAI,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAAE,OAAO,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,EAAE,CAAC,CAAC;IACvF,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC;IACnD,IAAI,MAAM,CAAC,MAAM;QACf,YAAY,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;AAClG,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CACvB,IAAY,EACZ,OAAe,EACf,OAAgD,EAAE;IAElD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,eAAe,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC,CAAC;QAC7E,MAAM,WAAW,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC;QACnD,IAAI,WAAW;YAAE,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC,CAAC,qEAAqE,CAAC,CAAC;IACjF,IAAI,CAAC;QACH,IAAI,IAAI,CAAC,UAAU,KAAK,KAAK;YAAE,wBAAwB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnE,MAAM,KAAK,GAAG,iBAAiB,CAAC,OAAO,EAAE,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;QAC7E,IAAI,KAAK;YAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC,CAAC,UAAU,CAAC,CAAC;IACtB,OAAO,EAAE,KAAK,EAAE,CAAC;AACnB,CAAC"}
|
package/dist/upkeep.d.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The version of the graft package this code was loaded from.
|
|
3
|
+
*
|
|
4
|
+
* Resolved from *this* module rather than the caller's: `readCurrentVersion`
|
|
5
|
+
* looks one level up from the module URL it's given, which lands on the package
|
|
6
|
+
* root for `dist/upkeep.js` but misses entirely for `dist/claude/hooks.js` and
|
|
7
|
+
* `dist/mcp/server.js`. Every caller asking here instead of passing its own
|
|
8
|
+
* `import.meta.url` is what keeps the hook and the MCP server from reading
|
|
9
|
+
* `0.0.0` and re-initing on every single session.
|
|
10
|
+
*/
|
|
11
|
+
export declare function runningVersion(): string;
|
|
12
|
+
/** How long a registry answer is considered current. A day: graft ships far less
|
|
13
|
+
* often than that, and this is a nudge, not a security update. */
|
|
14
|
+
export declare const UPDATE_TTL_MS: number;
|
|
15
|
+
/** Numeric-dotted compare of the release part only (`1.2.3-beta.1` → `1.2.3`).
|
|
16
|
+
* Prerelease ordering doesn't matter here: the only question either caller asks
|
|
17
|
+
* is "is the thing on npm ahead of what's on disk", and a prerelease that
|
|
18
|
+
* compares equal simply produces no nudge. */
|
|
19
|
+
export declare function compareVersions(a: string, b: string): number;
|
|
20
|
+
export declare function isNewer(candidate: string | null | undefined, current: string): boolean;
|
|
21
|
+
export interface UpdateCache {
|
|
22
|
+
/** Latest version seen on npm, or null when the last fetch failed. */
|
|
23
|
+
latest: string | null;
|
|
24
|
+
/** Epoch ms of the last *attempt* — written by the parent before it spawns the
|
|
25
|
+
* fetch, so a repeatedly-failing fetch can't make every command spawn a child. */
|
|
26
|
+
checkedAt: number;
|
|
27
|
+
}
|
|
28
|
+
/** Machine-global, not per-repo: "what's the latest graft" is one fact, and a
|
|
29
|
+
* dev with twelve repos should cost the registry one request a day, not twelve. */
|
|
30
|
+
export declare function updateCachePath(home?: string): string;
|
|
31
|
+
export declare function readUpdateCache(home?: string): UpdateCache | null;
|
|
32
|
+
/**
|
|
33
|
+
* The `graft _update-check` command body: hit the registry, store the answer.
|
|
34
|
+
* Runs in a detached child so nothing user-facing ever waits on the network.
|
|
35
|
+
*/
|
|
36
|
+
export declare function refreshUpdateCache(home?: string, now?: number): UpdateCache;
|
|
37
|
+
/** True when the cached answer is missing or older than the TTL. */
|
|
38
|
+
export declare function needsRefresh(cache: UpdateCache | null, now?: number): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Kick off a background registry check if the cache has gone stale. Touches
|
|
41
|
+
* `checkedAt` first so concurrent callers (and a child that dies) don't spawn a
|
|
42
|
+
* fetch per invocation. Never waits, never throws.
|
|
43
|
+
*
|
|
44
|
+
* Only called from long-lived or already-slow contexts (a CLI command, MCP
|
|
45
|
+
* boot) — never from a hook, which reads the cache and nothing else.
|
|
46
|
+
*/
|
|
47
|
+
export declare function maybeRefreshInBackground(home?: string, now?: number): boolean;
|
|
48
|
+
/** One line, or nothing. Nothing is the common case — don't spend context on
|
|
49
|
+
* "you're up to date". */
|
|
50
|
+
export declare function formatUpdateNudge(current: string, latest: string | null | undefined): string | null;
|
|
51
|
+
/**
|
|
52
|
+
* The subset of `graft init`'s flags a refresh has to replay.
|
|
53
|
+
*
|
|
54
|
+
* Without these, an auto-refresh would install things the user explicitly
|
|
55
|
+
* declined: someone who ran `graft init --no-global` (or `--no-hooks`) said "keep
|
|
56
|
+
* out of `~/.codex`", and a later session silently writing there would be graft
|
|
57
|
+
* overriding a decision rather than maintaining one. Absent from an older stamp →
|
|
58
|
+
* all true, which is what plain `graft init` does.
|
|
59
|
+
*/
|
|
60
|
+
export interface WiringOpts {
|
|
61
|
+
/** false → never write outside the repo (`--no-global`). */
|
|
62
|
+
global: boolean;
|
|
63
|
+
/** false → skip MCP server registration (`--no-mcp`). */
|
|
64
|
+
mcp: boolean;
|
|
65
|
+
/** false → skip hook installation (`--no-hooks`). */
|
|
66
|
+
hooks: boolean;
|
|
67
|
+
}
|
|
68
|
+
export declare const DEFAULT_WIRING_OPTS: WiringOpts;
|
|
69
|
+
/** An older stamp has no `opts`; a plain `graft init` wired everything. */
|
|
70
|
+
export declare function wiringOpts(stamp: WiringStamp | null): WiringOpts;
|
|
71
|
+
export interface WiringStamp {
|
|
72
|
+
/** The graft version whose `init` wrote this repo's agent files. */
|
|
73
|
+
version: string;
|
|
74
|
+
/** Host ids that were wired, so a refresh re-writes exactly those and never
|
|
75
|
+
* silently adopts an agent the user declined in the picker. */
|
|
76
|
+
hosts: string[];
|
|
77
|
+
/** The init flags to replay — see {@link WiringOpts}. */
|
|
78
|
+
opts?: Partial<WiringOpts>;
|
|
79
|
+
at: string;
|
|
80
|
+
}
|
|
81
|
+
/** Under `graft/.cache/`, beside the other derived state: git-ignored, per-clone,
|
|
82
|
+
* and cheap to lose — a missing stamp just means one idempotent refresh. */
|
|
83
|
+
export declare function stampPath(repo: string): string;
|
|
84
|
+
export declare function readStamp(repo: string): WiringStamp | null;
|
|
85
|
+
export declare function writeStamp(repo: string, version: string, hosts: string[], opts?: Partial<WiringOpts>, at?: string): void;
|
|
86
|
+
/**
|
|
87
|
+
* Which agents this repo is *already* wired for, read off disk rather than
|
|
88
|
+
* re-detected. Detection answers "which editors does this machine have"; for a
|
|
89
|
+
* refresh we need "which files did a previous init actually write" — otherwise
|
|
90
|
+
* installing Windsurf once would silently add graft rules to every repo.
|
|
91
|
+
*/
|
|
92
|
+
export declare function wiredHostIds(repo: string): string[];
|
|
93
|
+
export interface WiringRefresh {
|
|
94
|
+
from: string;
|
|
95
|
+
to: string;
|
|
96
|
+
hosts: string[];
|
|
97
|
+
/** True when the refresh included writes outside the repo (`~/.codex/`), so the
|
|
98
|
+
* caller can say so — a session changing machine-wide config should be visible. */
|
|
99
|
+
global: boolean;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Re-run init's writes when the stamp and the running binary disagree.
|
|
103
|
+
*
|
|
104
|
+
* Deliberately narrow: it refreshes the hosts already wired, replays the flags
|
|
105
|
+
* that init was given, never builds the graph (this runs at session start — a
|
|
106
|
+
* rebuild there would stall the agent's first turn), and no-ops when the repo has
|
|
107
|
+
* no graft wiring at all.
|
|
108
|
+
*/
|
|
109
|
+
export declare function reconcileWiring(repo: string, current: string, deps: {
|
|
110
|
+
wired?: (repo: string) => string[];
|
|
111
|
+
rewrite: (repo: string, hosts: string[], opts: WiringOpts) => void;
|
|
112
|
+
}): WiringRefresh | null;
|
|
113
|
+
export declare function formatWiringRefresh(r: WiringRefresh | null): string | null;
|
|
114
|
+
//# sourceMappingURL=upkeep.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upkeep.d.ts","sourceRoot":"","sources":["../src/upkeep.ts"],"names":[],"mappings":"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"}
|
package/dist/upkeep.js
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self-maintenance: keeping an installed graft, and the wiring it wrote, current.
|
|
3
|
+
*
|
|
4
|
+
* Two independent kinds of staleness, both invisible to the user today:
|
|
5
|
+
*
|
|
6
|
+
* 1. **Binary staleness** — they installed once and never upgraded. `graft
|
|
7
|
+
* version` would tell them, but nobody runs it. So we keep a cached,
|
|
8
|
+
* machine-global registry answer and let every entry point surface a
|
|
9
|
+
* one-line nudge from it.
|
|
10
|
+
*
|
|
11
|
+
* 2. **Wiring staleness** — `graft init` copies hooks, shims, skill text and
|
|
12
|
+
* rule files INTO the repo. `npm i -g` replaces the binary but touches none
|
|
13
|
+
* of them, so a repo wired by 0.7 keeps 0.7's prompts and 0.7's hook
|
|
14
|
+
* timeouts forever (see the comment on `promptAskTimeout`, which exists
|
|
15
|
+
* only to work around exactly this). A version stamp written next to the
|
|
16
|
+
* graph lets any entry point notice the skew and re-run the writes.
|
|
17
|
+
*
|
|
18
|
+
* Everything here is fail-soft by construction: it runs inside hooks and inside
|
|
19
|
+
* the MCP server's boot path, where a throw is a broken session, and it must
|
|
20
|
+
* never block on the network — the registry is only ever read from cache, and
|
|
21
|
+
* the refresh happens in a detached child.
|
|
22
|
+
*
|
|
23
|
+
* Called from all three hosts' entry points so no editor is left out:
|
|
24
|
+
* • Claude Code — `session-start` hook (src/claude/hooks.ts)
|
|
25
|
+
* • Cursor/Codex — MCP server boot (src/mcp/server.ts), and any CLI command
|
|
26
|
+
*/
|
|
27
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
28
|
+
import { spawn } from 'node:child_process';
|
|
29
|
+
import { join } from 'node:path';
|
|
30
|
+
import { homedir } from 'node:os';
|
|
31
|
+
import { readJson, writeJsonAtomic, cacheDir } from './util/state.js';
|
|
32
|
+
import { HOSTS } from './hosts/registry.js';
|
|
33
|
+
import { START } from './hosts/sections.js';
|
|
34
|
+
import { getNpmViewVersion, readCurrentVersion } from './cli-meta.js';
|
|
35
|
+
import { graftCliPath } from './claude/paths.js';
|
|
36
|
+
/**
|
|
37
|
+
* The version of the graft package this code was loaded from.
|
|
38
|
+
*
|
|
39
|
+
* Resolved from *this* module rather than the caller's: `readCurrentVersion`
|
|
40
|
+
* looks one level up from the module URL it's given, which lands on the package
|
|
41
|
+
* root for `dist/upkeep.js` but misses entirely for `dist/claude/hooks.js` and
|
|
42
|
+
* `dist/mcp/server.js`. Every caller asking here instead of passing its own
|
|
43
|
+
* `import.meta.url` is what keeps the hook and the MCP server from reading
|
|
44
|
+
* `0.0.0` and re-initing on every single session.
|
|
45
|
+
*/
|
|
46
|
+
export function runningVersion() {
|
|
47
|
+
try {
|
|
48
|
+
return readCurrentVersion(import.meta.url);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return '0.0.0';
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** How long a registry answer is considered current. A day: graft ships far less
|
|
55
|
+
* often than that, and this is a nudge, not a security update. */
|
|
56
|
+
export const UPDATE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
57
|
+
/* -------------------------------------------------------------------------- */
|
|
58
|
+
/* version comparison */
|
|
59
|
+
/* -------------------------------------------------------------------------- */
|
|
60
|
+
/** Numeric-dotted compare of the release part only (`1.2.3-beta.1` → `1.2.3`).
|
|
61
|
+
* Prerelease ordering doesn't matter here: the only question either caller asks
|
|
62
|
+
* is "is the thing on npm ahead of what's on disk", and a prerelease that
|
|
63
|
+
* compares equal simply produces no nudge. */
|
|
64
|
+
export function compareVersions(a, b) {
|
|
65
|
+
const parts = (v) => String(v).split('-')[0].split('.').map((n) => Number(n) || 0);
|
|
66
|
+
const pa = parts(a);
|
|
67
|
+
const pb = parts(b);
|
|
68
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
69
|
+
const d = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
70
|
+
if (d !== 0)
|
|
71
|
+
return d < 0 ? -1 : 1;
|
|
72
|
+
}
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
export function isNewer(candidate, current) {
|
|
76
|
+
if (!candidate)
|
|
77
|
+
return false;
|
|
78
|
+
return compareVersions(candidate, current) > 0;
|
|
79
|
+
}
|
|
80
|
+
/** Machine-global, not per-repo: "what's the latest graft" is one fact, and a
|
|
81
|
+
* dev with twelve repos should cost the registry one request a day, not twelve. */
|
|
82
|
+
export function updateCachePath(home = homedir()) {
|
|
83
|
+
return join(home, '.graft', 'update-check.json');
|
|
84
|
+
}
|
|
85
|
+
export function readUpdateCache(home) {
|
|
86
|
+
return readJson(updateCachePath(home));
|
|
87
|
+
}
|
|
88
|
+
function writeUpdateCache(cache, home) {
|
|
89
|
+
try {
|
|
90
|
+
writeJsonAtomic(updateCachePath(home), cache);
|
|
91
|
+
}
|
|
92
|
+
catch { /* unwritable home — skip */ }
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The `graft _update-check` command body: hit the registry, store the answer.
|
|
96
|
+
* Runs in a detached child so nothing user-facing ever waits on the network.
|
|
97
|
+
*/
|
|
98
|
+
export function refreshUpdateCache(home, now = Date.now()) {
|
|
99
|
+
const res = getNpmViewVersion();
|
|
100
|
+
const cache = { latest: res.ok ? (res.version ?? null) : null, checkedAt: now };
|
|
101
|
+
writeUpdateCache(cache, home);
|
|
102
|
+
return cache;
|
|
103
|
+
}
|
|
104
|
+
/** True when the cached answer is missing or older than the TTL. */
|
|
105
|
+
export function needsRefresh(cache, now = Date.now()) {
|
|
106
|
+
return !cache || typeof cache.checkedAt !== 'number' || now - cache.checkedAt >= UPDATE_TTL_MS;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Kick off a background registry check if the cache has gone stale. Touches
|
|
110
|
+
* `checkedAt` first so concurrent callers (and a child that dies) don't spawn a
|
|
111
|
+
* fetch per invocation. Never waits, never throws.
|
|
112
|
+
*
|
|
113
|
+
* Only called from long-lived or already-slow contexts (a CLI command, MCP
|
|
114
|
+
* boot) — never from a hook, which reads the cache and nothing else.
|
|
115
|
+
*/
|
|
116
|
+
export function maybeRefreshInBackground(home, now = Date.now()) {
|
|
117
|
+
const cache = readUpdateCache(home);
|
|
118
|
+
if (!needsRefresh(cache, now))
|
|
119
|
+
return false;
|
|
120
|
+
writeUpdateCache({ latest: cache?.latest ?? null, checkedAt: now }, home);
|
|
121
|
+
try {
|
|
122
|
+
const child = spawn(process.execPath, [graftCliPath(), '_update-check'], {
|
|
123
|
+
detached: true,
|
|
124
|
+
stdio: 'ignore',
|
|
125
|
+
});
|
|
126
|
+
child.unref();
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return false; // no spawn (sandbox, ENOMEM) — the nudge just uses the old cache
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/** One line, or nothing. Nothing is the common case — don't spend context on
|
|
134
|
+
* "you're up to date". */
|
|
135
|
+
export function formatUpdateNudge(current, latest) {
|
|
136
|
+
if (!isNewer(latest, current))
|
|
137
|
+
return null;
|
|
138
|
+
return `⬆ graft ${current} → ${latest} available: run \`npm i -g @nanonets/graft@latest\` (restart your agent after).`;
|
|
139
|
+
}
|
|
140
|
+
export const DEFAULT_WIRING_OPTS = { global: true, mcp: true, hooks: true };
|
|
141
|
+
/** An older stamp has no `opts`; a plain `graft init` wired everything. */
|
|
142
|
+
export function wiringOpts(stamp) {
|
|
143
|
+
return { ...DEFAULT_WIRING_OPTS, ...(stamp?.opts ?? {}) };
|
|
144
|
+
}
|
|
145
|
+
/** Under `graft/.cache/`, beside the other derived state: git-ignored, per-clone,
|
|
146
|
+
* and cheap to lose — a missing stamp just means one idempotent refresh. */
|
|
147
|
+
export function stampPath(repo) {
|
|
148
|
+
return join(cacheDir(repo), 'wiring-stamp.json');
|
|
149
|
+
}
|
|
150
|
+
export function readStamp(repo) {
|
|
151
|
+
return readJson(stampPath(repo));
|
|
152
|
+
}
|
|
153
|
+
export function writeStamp(repo, version, hosts, opts = {}, at = new Date().toISOString()) {
|
|
154
|
+
try {
|
|
155
|
+
writeJsonAtomic(stampPath(repo), {
|
|
156
|
+
version,
|
|
157
|
+
hosts: [...hosts].sort(),
|
|
158
|
+
opts: { ...DEFAULT_WIRING_OPTS, ...opts },
|
|
159
|
+
at,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
catch { /* unwritable graft/ — a refresh will just be retried next session */ }
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Which agents this repo is *already* wired for, read off disk rather than
|
|
166
|
+
* re-detected. Detection answers "which editors does this machine have"; for a
|
|
167
|
+
* refresh we need "which files did a previous init actually write" — otherwise
|
|
168
|
+
* installing Windsurf once would silently add graft rules to every repo.
|
|
169
|
+
*/
|
|
170
|
+
export function wiredHostIds(repo) {
|
|
171
|
+
const ids = [];
|
|
172
|
+
if (existsSync(join(repo, '.claude', 'helpers', 'graft-hooks.cjs')))
|
|
173
|
+
ids.push('claude');
|
|
174
|
+
for (const host of HOSTS) {
|
|
175
|
+
const path = join(repo, host.relPath);
|
|
176
|
+
if (!existsSync(path))
|
|
177
|
+
continue;
|
|
178
|
+
// A shared file (AGENTS.md, GEMINI.md) counts only if graft's fenced section
|
|
179
|
+
// is in it — the user may own the file for entirely unrelated reasons.
|
|
180
|
+
if (host.kind === 'section') {
|
|
181
|
+
try {
|
|
182
|
+
if (!readFileSync(path, 'utf8').includes(START))
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
ids.push(host.id);
|
|
190
|
+
}
|
|
191
|
+
return ids;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Re-run init's writes when the stamp and the running binary disagree.
|
|
195
|
+
*
|
|
196
|
+
* Deliberately narrow: it refreshes the hosts already wired, replays the flags
|
|
197
|
+
* that init was given, never builds the graph (this runs at session start — a
|
|
198
|
+
* rebuild there would stall the agent's first turn), and no-ops when the repo has
|
|
199
|
+
* no graft wiring at all.
|
|
200
|
+
*/
|
|
201
|
+
export function reconcileWiring(repo, current, deps) {
|
|
202
|
+
try {
|
|
203
|
+
const stamp = readStamp(repo);
|
|
204
|
+
if (stamp && stamp.version === current)
|
|
205
|
+
return null;
|
|
206
|
+
// The stamp is the record of *intent* (what the picker chose); disk is the
|
|
207
|
+
// fallback for repos wired before stamps existed. Union, not just disk:
|
|
208
|
+
// otherwise a host whose rule file went missing — deleted by hand, lost to a
|
|
209
|
+
// merge, or clobbered by another tool — is silently dropped from every future
|
|
210
|
+
// refresh, and that file is precisely what a refresh exists to restore.
|
|
211
|
+
const onDisk = (deps.wired ?? wiredHostIds)(repo);
|
|
212
|
+
const hosts = [...new Set([...(stamp?.hosts ?? []), ...onDisk])].sort();
|
|
213
|
+
if (hosts.length === 0)
|
|
214
|
+
return null; // never wired here — not our business
|
|
215
|
+
const opts = wiringOpts(stamp);
|
|
216
|
+
deps.rewrite(repo, hosts, opts);
|
|
217
|
+
writeStamp(repo, current, hosts, opts);
|
|
218
|
+
return { from: stamp?.version ?? 'unwired', to: current, hosts, global: opts.global };
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
return null; // a refresh is an optimization; never fail the caller over it
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
export function formatWiringRefresh(r) {
|
|
225
|
+
if (!r)
|
|
226
|
+
return null;
|
|
227
|
+
// Name the out-of-repo writes explicitly: those are machine-wide and shared by
|
|
228
|
+
// every repo, so a user seeing this line should not have to guess what moved.
|
|
229
|
+
const scope = r.global && r.hosts.includes('agents') ? " (including this machine's ~/.codex config)" : '';
|
|
230
|
+
return `· graft refreshed this repo's agent wiring${scope} (written by ${r.from}, now ${r.to}): ${r.hosts.join(', ')}.`;
|
|
231
|
+
}
|
|
232
|
+
//# sourceMappingURL=upkeep.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upkeep.js","sourceRoot":"","sources":["../src/upkeep.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"num.d.ts","sourceRoot":"","sources":["../../src/util/num.ts"],"names":[],"mappings":"AAgBA,+DAA+D;AAC/D,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE7C"}
|
package/dist/util/num.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Number formatting for text graft emits.
|
|
3
|
+
*
|
|
4
|
+
* `toLocaleString()` with no locale follows the MACHINE's locale, which is the
|
|
5
|
+
* wrong authority for two of graft's outputs. The savings lines are read by an
|
|
6
|
+
* LLM and explicitly ask it to sum them across calls; on a pt-BR/de-DE box the
|
|
7
|
+
* bare call renders 1990 as "1.990", which an agent reads as 1.99 — so the
|
|
8
|
+
* headline "tokens saved this turn" total comes out ~1000× low. The graph's own
|
|
9
|
+
* stats are compared across machines and CI, where a separator that moves with
|
|
10
|
+
* the host makes two identical runs look different.
|
|
11
|
+
*
|
|
12
|
+
* One pinned locale for both: the output is a data interchange format that
|
|
13
|
+
* happens to be human-readable, not localized UI.
|
|
14
|
+
*/
|
|
15
|
+
const FORMATTER = new Intl.NumberFormat("en-US");
|
|
16
|
+
/** Thousands-separated integer, identical on every machine. */
|
|
17
|
+
export function formatCount(n) {
|
|
18
|
+
return FORMATTER.format(Math.round(n));
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=num.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"num.js","sourceRoot":"","sources":["../../src/util/num.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;AAEjD,+DAA+D;AAC/D,MAAM,UAAU,WAAW,CAAC,CAAS;IACnC,OAAO,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AACzC,CAAC"}
|
package/dist/util/state.d.ts
CHANGED
|
@@ -32,16 +32,18 @@ export declare function writeJsonAtomic(p: string, value: unknown, compact?: boo
|
|
|
32
32
|
export declare function readStats(d: string): Stats | null;
|
|
33
33
|
export declare function writeStats(d: string, s: Stats): void;
|
|
34
34
|
/**
|
|
35
|
-
* Persisted per-repo build configuration
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* skipped), so a repo that never used the flag is entirely unaffected.
|
|
35
|
+
* Persisted per-repo build configuration. Explicit CLI choices live here so
|
|
36
|
+
* later no-flag builds and automatic refreshes enumerate the same file set.
|
|
37
|
+
* Missing fields always retain backwards-compatible defaults.
|
|
39
38
|
*/
|
|
40
39
|
export interface BuildConfig {
|
|
41
40
|
/** SKIP_DIRS names to include in this repo's walks, persisted so a LATER
|
|
42
41
|
* no-flag build — and the fingerprint/refresh path, which never sees CLI
|
|
43
42
|
* flags at all — behave identically to the invocation that set it. */
|
|
44
43
|
includeDirs?: string[];
|
|
44
|
+
/** Whether initialized Git submodules are folded into this repo's graph.
|
|
45
|
+
* Absent/false keeps the historical boundary at the superproject. */
|
|
46
|
+
followSubmodules?: boolean;
|
|
45
47
|
}
|
|
46
48
|
/** Local, Git-ignored repository configuration. Kept outside generated
|
|
47
49
|
* `graft/` output so deleting/replacing that cache, workspace federation, and
|
|
@@ -50,12 +52,17 @@ export declare const BUILD_CONFIG_DIR = ".graft";
|
|
|
50
52
|
export declare function buildConfigPath(d: string): string;
|
|
51
53
|
export declare function readBuildConfig(d: string): BuildConfig | null;
|
|
52
54
|
export declare function writeBuildConfig(d: string, c: BuildConfig): void;
|
|
55
|
+
/** Merge explicit CLI choices into the existing local config, so updating one
|
|
56
|
+
* persisted build option cannot erase another. */
|
|
57
|
+
export declare function patchBuildConfig(d: string, patch: BuildConfig): void;
|
|
53
58
|
/** The persisted `--include-dir` override for repo `d`, as a Set — `undefined`
|
|
54
59
|
* when nothing was ever persisted (or the persisted list is empty), which every
|
|
55
60
|
* `shouldSkipDir`/`walkDir` caller treats as "today's default behavior". Shared
|
|
56
61
|
* by every walkDir-driven entry point (source-files.ts, scopes.ts) so a build,
|
|
57
62
|
* a later no-flag rebuild, and the hooks/refresh path all agree. */
|
|
58
63
|
export declare function readIncludeDirs(d: string): Set<string> | undefined;
|
|
64
|
+
/** Missing and explicit false both retain the backwards-compatible default. */
|
|
65
|
+
export declare function readFollowSubmodules(d: string): boolean;
|
|
59
66
|
export declare function patchStats(d: string, patch: Partial<Stats>): Stats;
|
|
60
67
|
export declare function acquireLock(d: string): boolean;
|
|
61
68
|
export declare function releaseLock(d: string): void;
|