dsh-graft-plugin 0.2.0 → 0.3.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/LICENSE +21 -21
- package/README.md +27 -1
- package/package.json +13 -8
- package/scripts/test-autosync.mjs +66 -1
- package/scripts/test-guidance.mjs +160 -0
- package/scripts/test-harness.mjs +219 -1
- package/src/auto-sync.js +34 -2
- package/src/client.js +438 -59
- package/src/graft-tools.js +11 -0
- package/src/index.js +190 -36
package/src/auto-sync.js
CHANGED
|
@@ -71,6 +71,10 @@ const DEFAULT_WRITE_TOOLS = ['write', 'edit', 'str_replace_editor', 'pwsh', 'bas
|
|
|
71
71
|
|
|
72
72
|
/** Graft's own state helpers, loaded from wherever graft is installed. */
|
|
73
73
|
async function loadGraftState(cliPath) {
|
|
74
|
+
// A cli we cannot locate (bare shim on PATH) must degrade to "unavailable",
|
|
75
|
+
// never throw: this runs inside fire-and-forget middleware and a rejection
|
|
76
|
+
// here is fatal to dsh.
|
|
77
|
+
if (typeof cliPath !== 'string' || cliPath === '') return undefined
|
|
74
78
|
const claudeDir = join(dirname(cliPath), 'claude')
|
|
75
79
|
const statePath = join(claudeDir, 'state.js')
|
|
76
80
|
const syncRun = join(claudeDir, 'sync-run.js')
|
|
@@ -132,7 +136,11 @@ export function createAutoSync(deps = {}) {
|
|
|
132
136
|
let loaded
|
|
133
137
|
const graft = async () => {
|
|
134
138
|
if (loaded === undefined) {
|
|
135
|
-
|
|
139
|
+
try {
|
|
140
|
+
loaded = deps.graft !== undefined ? await deps.graft : await loadGraftState(deps.cliPath)
|
|
141
|
+
} catch {
|
|
142
|
+
loaded = null // any loader failure counts as "graft unavailable"
|
|
143
|
+
}
|
|
136
144
|
if (loaded === undefined) loaded = null
|
|
137
145
|
}
|
|
138
146
|
return loaded
|
|
@@ -161,7 +169,7 @@ export function createAutoSync(deps = {}) {
|
|
|
161
169
|
root,
|
|
162
170
|
setTimer(() => {
|
|
163
171
|
timers.delete(root)
|
|
164
|
-
void api.syncIfDirty(root)
|
|
172
|
+
void api.syncIfDirty(root).catch(() => {})
|
|
165
173
|
}, debounceMs),
|
|
166
174
|
)
|
|
167
175
|
}
|
|
@@ -201,6 +209,30 @@ export function createAutoSync(deps = {}) {
|
|
|
201
209
|
}
|
|
202
210
|
if (stats?.dirty !== true && !pending.has(root)) return 'clean'
|
|
203
211
|
|
|
212
|
+
return api.rebuild(root)
|
|
213
|
+
},
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Rebuild one repo NOW, whether or not it looks dirty.
|
|
217
|
+
*
|
|
218
|
+
* The popup's Build button lands here, not in syncIfDirty: its gate reads
|
|
219
|
+
* graft's `dirty` flag, and a repo rebuilt by a plain `graft build` — which
|
|
220
|
+
* never patches stats.json — can read clean while being stale, answering
|
|
221
|
+
* 'clean' and spawning nothing. A pressed button that changes nothing is
|
|
222
|
+
* the failure this exists to prevent. The lock and the interval floor
|
|
223
|
+
* still apply: forced means "skip the dirt check", not "race a running
|
|
224
|
+
* build".
|
|
225
|
+
*
|
|
226
|
+
* @returns why it did or did not run, for the harness and the log.
|
|
227
|
+
*/
|
|
228
|
+
rebuild: async (root) => {
|
|
229
|
+
if (typeof root !== 'string' || root === '') return 'no-root'
|
|
230
|
+
const g = await graft()
|
|
231
|
+
if (g === null) return 'graft-unavailable'
|
|
232
|
+
|
|
233
|
+
const since = now() - (lastRun.get(root) ?? -Infinity)
|
|
234
|
+
if (since < minIntervalMs) return 'too-soon'
|
|
235
|
+
|
|
204
236
|
// The lock is graft's, so a rebuild already running — started here, or by
|
|
205
237
|
// Claude Code in the same repo — is left alone rather than raced.
|
|
206
238
|
try {
|