@adhdev/daemon-core 0.9.82-rc.418 → 0.9.82-rc.419
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/dist/index.js +40 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +40 -5
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/worktree-bootstrap-config.d.ts +1 -1
- package/dist/repo-mesh-types.d.ts +1 -1
- package/package.json +2 -2
- package/src/mesh/worktree-bootstrap-config.ts +79 -2
- package/src/repo-mesh-types.ts +3 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type MeshRefineValidationCommandPlan, type RepoMeshRefineValidationCommandConfig } from './refine-config.js';
|
|
2
2
|
import type { MeshAsyncJobLifecycle } from '../repo-mesh-types.js';
|
|
3
|
-
export type WorktreeBootstrapStatus = 'ready' | 'running' | 'failed' | 'not_configured' | 'disabled' | 'stale';
|
|
3
|
+
export type WorktreeBootstrapStatus = 'ready' | 'complete' | 'running' | 'failed' | 'not_configured' | 'disabled' | 'stale';
|
|
4
4
|
export interface RepoMeshWorktreeBootstrapConfig {
|
|
5
5
|
version: 1;
|
|
6
6
|
enabled?: boolean;
|
|
@@ -528,7 +528,7 @@ export interface LocalMeshNodeEntry {
|
|
|
528
528
|
clonedFromNodeId?: string;
|
|
529
529
|
/** Repo-local preparation result for ADHDev-created worktree nodes. */
|
|
530
530
|
worktreeBootstrap?: {
|
|
531
|
-
status: 'ready' | 'running' | 'failed' | 'not_configured' | 'disabled' | 'stale';
|
|
531
|
+
status: 'ready' | 'complete' | 'running' | 'failed' | 'not_configured' | 'disabled' | 'stale';
|
|
532
532
|
required?: boolean;
|
|
533
533
|
configSource?: string;
|
|
534
534
|
configSourceType?: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhdev/daemon-core",
|
|
3
|
-
"version": "0.9.82-rc.
|
|
3
|
+
"version": "0.9.82-rc.419",
|
|
4
4
|
"description": "ADHDev daemon core — CDP, IDE detection, providers, command execution",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"author": "vilmire",
|
|
47
47
|
"license": "AGPL-3.0-or-later",
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@adhdev/mesh-shared": "0.9.82-rc.
|
|
49
|
+
"@adhdev/mesh-shared": "0.9.82-rc.419",
|
|
50
50
|
"@adhdev/session-host-core": "*",
|
|
51
51
|
"@agentclientprotocol/sdk": "^0.16.1",
|
|
52
52
|
"ajv": "^8.20.0",
|
|
@@ -13,7 +13,12 @@ import {
|
|
|
13
13
|
} from './refine-config.js';
|
|
14
14
|
import type { MeshAsyncJobLifecycle } from '../repo-mesh-types.js';
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
// 'complete' is the terminal status the coordinator's markWorktreeBootstrapTerminalState
|
|
17
|
+
// (router.ts) stamps when the worktree_bootstrap_complete event fires — distinct from
|
|
18
|
+
// 'ready' (which carries a staleInputs digest from an in-process run). It is terminal and
|
|
19
|
+
// must be recognized as such by evaluateWorktreeBootstrapState so a later re-hydration never
|
|
20
|
+
// round-trips it back to 'stale'/'never_ran'.
|
|
21
|
+
export type WorktreeBootstrapStatus = 'ready' | 'complete' | 'running' | 'failed' | 'not_configured' | 'disabled' | 'stale';
|
|
17
22
|
|
|
18
23
|
export interface RepoMeshWorktreeBootstrapConfig {
|
|
19
24
|
version: 1;
|
|
@@ -56,6 +61,65 @@ export interface WorktreeBootstrapState extends MeshAsyncJobLifecycle {
|
|
|
56
61
|
// local workspace on disk, or git errors) it returns false and the gate stays closed.
|
|
57
62
|
export const WORKTREE_BOOTSTRAP_STALE_RUNNING_MS = 10 * 60 * 1000;
|
|
58
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Enumerate registered submodule paths for a worktree, generically (no hardcoded
|
|
66
|
+
* 'oss'/'adhdev-providers'). Reads `.gitmodules` via `git config --file .gitmodules
|
|
67
|
+
* --get-regexp path`, whose lines are `submodule.<name>.path <relativePath>`. Paths
|
|
68
|
+
* are returned normalized to forward slashes (git porcelain always emits '/'),
|
|
69
|
+
* trailing slash stripped. Returns an empty set when there are no submodules or the
|
|
70
|
+
* lookup fails — callers must then treat any change as dirty (conservative).
|
|
71
|
+
*/
|
|
72
|
+
function getRegisteredSubmodulePaths(workspace: string): Set<string> {
|
|
73
|
+
const paths = new Set<string>();
|
|
74
|
+
try {
|
|
75
|
+
const out = execFileSync(
|
|
76
|
+
resolveWin32Executable('git'),
|
|
77
|
+
['config', '--file', '.gitmodules', '--get-regexp', 'path'],
|
|
78
|
+
{ cwd: workspace, encoding: 'utf8', timeout: 10_000, windowsHide: true },
|
|
79
|
+
);
|
|
80
|
+
for (const line of String(out).split(/\r?\n/)) {
|
|
81
|
+
const trimmed = line.trim();
|
|
82
|
+
if (!trimmed) continue;
|
|
83
|
+
// `submodule.<name>.path <relativePath>` — value is everything after the first space.
|
|
84
|
+
const spaceIdx = trimmed.indexOf(' ');
|
|
85
|
+
if (spaceIdx < 0) continue;
|
|
86
|
+
const value = trimmed.slice(spaceIdx + 1).trim().replace(/\\/g, '/').replace(/\/+$/, '');
|
|
87
|
+
if (value) paths.add(value);
|
|
88
|
+
}
|
|
89
|
+
} catch {
|
|
90
|
+
// No .gitmodules / not a submodule superproject / git error → no exemptions.
|
|
91
|
+
}
|
|
92
|
+
return paths;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* True when `git status --porcelain` output represents a worktree that is clean
|
|
97
|
+
* EXCEPT for submodule-gitlink-pointer moves. A worktree task that commits inside a
|
|
98
|
+
* registered submodule (e.g. oss/) leaves the superproject's gitlink outOfSync — a
|
|
99
|
+
* porcelain line of exactly " M <submodulePath>" (or "M <submodulePath>") — which is
|
|
100
|
+
* a NORMAL product of that task and must NOT disqualify the stale backstop. Any other
|
|
101
|
+
* line (a real file edit, an untracked file, a staged add, an unmerged path) means the
|
|
102
|
+
* tree is genuinely dirty → not clean. We filter gitlink lines explicitly rather than
|
|
103
|
+
* using `git status --ignore-submodules=all`, which would also mask a submodule with
|
|
104
|
+
* genuinely-uncommitted *content* (=dirty still surfaces pointer moves we want to ignore;
|
|
105
|
+
* =all would over-suppress).
|
|
106
|
+
*/
|
|
107
|
+
function isCleanIgnoringSubmoduleGitlinks(porcelain: string, submodulePaths: Set<string>): boolean {
|
|
108
|
+
const lines = porcelain.split(/\r?\n/).filter(line => line.length > 0);
|
|
109
|
+
for (const line of lines) {
|
|
110
|
+
// Porcelain v1 line: two status chars (XY) + space + path. A submodule gitlink
|
|
111
|
+
// pointer move shows X or Y as 'M' with the other position a space, e.g.
|
|
112
|
+
// " M oss" (worktree-modified) or "M oss" (index-modified). Quoted paths
|
|
113
|
+
// (core.quotePath) wrap in double-quotes — those never match a bare submodule path,
|
|
114
|
+
// so they correctly fall through as dirty.
|
|
115
|
+
const status = line.slice(0, 2);
|
|
116
|
+
const path = line.slice(3).trim().replace(/\\/g, '/').replace(/\/+$/, '');
|
|
117
|
+
const isGitlinkPointerMove = (status === ' M' || status === 'M ') && submodulePaths.has(path);
|
|
118
|
+
if (!isGitlinkPointerMove) return false; // any non-gitlink change → dirty
|
|
119
|
+
}
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
|
|
59
123
|
export function isWorktreeBootstrapStaleRunning(
|
|
60
124
|
node: { worktreeBootstrap?: { status?: string; startedAt?: string; updatedAt?: string; completedAt?: string }; workspace?: string } | undefined,
|
|
61
125
|
nowMs: number = Date.now(),
|
|
@@ -76,7 +140,15 @@ export function isWorktreeBootstrapStaleRunning(
|
|
|
76
140
|
timeout: 10_000,
|
|
77
141
|
windowsHide: true,
|
|
78
142
|
});
|
|
79
|
-
|
|
143
|
+
const porcelain = String(out).replace(/\r?\n$/, '');
|
|
144
|
+
if (porcelain.trim() === '') return true; // truly clean
|
|
145
|
+
// A worktree task that committed inside a submodule leaves the superproject's
|
|
146
|
+
// submodule-gitlink pointer outOfSync (" M oss"); that is the normal aftermath of the
|
|
147
|
+
// task this backstop exists to unstick, not an in-progress bootstrap, so a tree dirty
|
|
148
|
+
// ONLY by gitlink pointer moves still counts as clean here.
|
|
149
|
+
const submodulePaths = getRegisteredSubmodulePaths(workspace);
|
|
150
|
+
if (submodulePaths.size === 0) return false; // no submodules → no exemption, dirty
|
|
151
|
+
return isCleanIgnoringSubmoduleGitlinks(porcelain, submodulePaths);
|
|
80
152
|
} catch {
|
|
81
153
|
return false; // cannot verify clean → stay conservative (gate remains closed)
|
|
82
154
|
}
|
|
@@ -238,6 +310,11 @@ export function evaluateWorktreeBootstrapState(mesh: any, workspace: string, per
|
|
|
238
310
|
}
|
|
239
311
|
if (persisted?.status === 'running') return { ...persisted, required };
|
|
240
312
|
if (persisted?.status === 'failed') return { ...persisted, required };
|
|
313
|
+
// 'complete' is the terminal stamp written by markWorktreeBootstrapTerminalState
|
|
314
|
+
// (router.ts) on the worktree_bootstrap_complete event. Pass it through unchanged so a
|
|
315
|
+
// later getMeshWithCache re-hydration never re-derives it back to 'stale'/'never_ran' —
|
|
316
|
+
// doing so would reopen the dispatch/claim gate against a node whose bootstrap is done.
|
|
317
|
+
if (persisted?.status === 'complete') return { ...persisted, required };
|
|
241
318
|
if (persisted?.status === 'ready') {
|
|
242
319
|
const staleInputs = loaded.config.staleInputs ?? persisted.staleInputs ?? [];
|
|
243
320
|
if (staleInputs.length > 0 && persisted.staleInputsDigest) {
|
package/src/repo-mesh-types.ts
CHANGED
|
@@ -769,7 +769,9 @@ export interface LocalMeshNodeEntry {
|
|
|
769
769
|
clonedFromNodeId?: string;
|
|
770
770
|
/** Repo-local preparation result for ADHDev-created worktree nodes. */
|
|
771
771
|
worktreeBootstrap?: {
|
|
772
|
-
|
|
772
|
+
// 'complete' is the terminal stamp written by markWorktreeBootstrapTerminalState
|
|
773
|
+
// (router.ts) on worktree_bootstrap_complete — kept in sync with WorktreeBootstrapStatus.
|
|
774
|
+
status: 'ready' | 'complete' | 'running' | 'failed' | 'not_configured' | 'disabled' | 'stale';
|
|
773
775
|
required?: boolean;
|
|
774
776
|
configSource?: string;
|
|
775
777
|
configSourceType?: string;
|