@rasensio/aidlc 1.4.0 → 1.6.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.
Files changed (86) hide show
  1. package/dist/commands/claim.d.ts +8 -6
  2. package/dist/commands/claim.d.ts.map +1 -1
  3. package/dist/commands/claim.js +49 -41
  4. package/dist/commands/claim.js.map +1 -1
  5. package/dist/commands/gate.d.ts.map +1 -1
  6. package/dist/commands/gate.js +20 -2
  7. package/dist/commands/gate.js.map +1 -1
  8. package/dist/commands/init.js +13 -0
  9. package/dist/commands/init.js.map +1 -1
  10. package/dist/commands/start.d.ts.map +1 -1
  11. package/dist/commands/start.js +145 -4
  12. package/dist/commands/start.js.map +1 -1
  13. package/dist/commands/status.d.ts.map +1 -1
  14. package/dist/commands/status.js +110 -3
  15. package/dist/commands/status.js.map +1 -1
  16. package/dist/commands/transition.d.ts.map +1 -1
  17. package/dist/commands/transition.js +39 -4
  18. package/dist/commands/transition.js.map +1 -1
  19. package/dist/commands/update.d.ts.map +1 -1
  20. package/dist/commands/update.js +13 -0
  21. package/dist/commands/update.js.map +1 -1
  22. package/dist/concurrency/claim-store.d.ts +44 -0
  23. package/dist/concurrency/claim-store.d.ts.map +1 -0
  24. package/dist/concurrency/claim-store.js +273 -0
  25. package/dist/concurrency/claim-store.js.map +1 -0
  26. package/dist/concurrency/common-dir.d.ts +25 -0
  27. package/dist/concurrency/common-dir.d.ts.map +1 -0
  28. package/dist/concurrency/common-dir.js +81 -0
  29. package/dist/concurrency/common-dir.js.map +1 -0
  30. package/dist/concurrency/detect.d.ts +23 -0
  31. package/dist/concurrency/detect.d.ts.map +1 -0
  32. package/dist/concurrency/detect.js +59 -0
  33. package/dist/concurrency/detect.js.map +1 -0
  34. package/dist/concurrency/gitattributes.d.ts +23 -0
  35. package/dist/concurrency/gitattributes.d.ts.map +1 -0
  36. package/dist/concurrency/gitattributes.js +52 -0
  37. package/dist/concurrency/gitattributes.js.map +1 -0
  38. package/dist/concurrency/registry.d.ts +48 -0
  39. package/dist/concurrency/registry.d.ts.map +1 -0
  40. package/dist/concurrency/registry.js +163 -0
  41. package/dist/concurrency/registry.js.map +1 -0
  42. package/dist/concurrency/session-id.d.ts +22 -0
  43. package/dist/concurrency/session-id.d.ts.map +1 -0
  44. package/dist/concurrency/session-id.js +35 -0
  45. package/dist/concurrency/session-id.js.map +1 -0
  46. package/dist/concurrency/types.d.ts +39 -0
  47. package/dist/concurrency/types.d.ts.map +1 -0
  48. package/dist/concurrency/types.js +12 -0
  49. package/dist/concurrency/types.js.map +1 -0
  50. package/dist/concurrency/worktree.d.ts +49 -0
  51. package/dist/concurrency/worktree.d.ts.map +1 -0
  52. package/dist/concurrency/worktree.js +169 -0
  53. package/dist/concurrency/worktree.js.map +1 -0
  54. package/dist/core/gate.d.ts +19 -1
  55. package/dist/core/gate.d.ts.map +1 -1
  56. package/dist/core/gate.js +18 -1
  57. package/dist/core/gate.js.map +1 -1
  58. package/dist/core/lifecycle.d.ts.map +1 -1
  59. package/dist/core/lifecycle.js +7 -3
  60. package/dist/core/lifecycle.js.map +1 -1
  61. package/dist/core/types.d.ts +16 -1
  62. package/dist/core/types.d.ts.map +1 -1
  63. package/dist/cost/ledger.d.ts.map +1 -1
  64. package/dist/cost/ledger.js +6 -0
  65. package/dist/cost/ledger.js.map +1 -1
  66. package/dist/cost/sessions.d.ts +6 -1
  67. package/dist/cost/sessions.d.ts.map +1 -1
  68. package/dist/cost/sessions.js +10 -1
  69. package/dist/cost/sessions.js.map +1 -1
  70. package/dist/tasks/gate-criterion.d.ts +42 -0
  71. package/dist/tasks/gate-criterion.d.ts.map +1 -0
  72. package/dist/tasks/gate-criterion.js +180 -0
  73. package/dist/tasks/gate-criterion.js.map +1 -0
  74. package/dist/tasks/parser.d.ts +30 -0
  75. package/dist/tasks/parser.d.ts.map +1 -0
  76. package/dist/tasks/parser.js +200 -0
  77. package/dist/tasks/parser.js.map +1 -0
  78. package/dist/tasks/status.d.ts +20 -0
  79. package/dist/tasks/status.d.ts.map +1 -0
  80. package/dist/tasks/status.js +41 -0
  81. package/dist/tasks/status.js.map +1 -0
  82. package/dist/tasks/types.d.ts +90 -0
  83. package/dist/tasks/types.d.ts.map +1 -0
  84. package/dist/tasks/types.js +9 -0
  85. package/dist/tasks/types.js.map +1 -0
  86. package/package.json +2 -2
@@ -0,0 +1,273 @@
1
+ /**
2
+ * Claim store: live instance claims in the git common dir (AC-1…AC-6).
3
+ *
4
+ * One YAML file per instance under `<common-dir>/aidlc/claims/` (design
5
+ * DD2): per-file writes need no cross-instance locking and unlink is an
6
+ * atomic release. Write discipline is read-check-write-recheck-rename
7
+ * (NFR-2); the residual TOCTOU window is accepted — claims are advisory
8
+ * coordination, not mutual exclusion.
9
+ *
10
+ * Legacy migration (AC-4, AC-23): a non-null `claim:` in instance.yaml is
11
+ * moved into the store on first read; on conflict the common-dir claim
12
+ * wins ("common-dir precedence" — the new store is authoritative once
13
+ * populated). instance.yaml always ends with `claim: null`.
14
+ *
15
+ * Outside a git repo (AC-5) or with an unwritable common dir (AC-27) the
16
+ * legacy instance.yaml path keeps working via `openClaimStore` returning
17
+ * a store backed by instance.yaml.
18
+ */
19
+ import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, unlinkSync, writeFileSync, } from 'node:fs';
20
+ import { join } from 'node:path';
21
+ import { parse as parseYaml } from 'yaml';
22
+ import { loadYaml, saveYaml, serializeYaml } from '../state/yaml-helpers.js';
23
+ import { coordinationRoot } from './common-dir.js';
24
+ const INSTANCE_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
25
+ /** Thrown when a write would override another session's live claim (AC-6). */
26
+ export class ClaimConflictError extends Error {
27
+ instance;
28
+ holder;
29
+ constructor(instance, holder) {
30
+ super(`${instance} is claimed by session ${holder} (not stale)`);
31
+ this.instance = instance;
32
+ this.holder = holder;
33
+ this.name = 'ClaimConflictError';
34
+ }
35
+ }
36
+ export function isStale(claim, timeoutMinutes, now = new Date()) {
37
+ const last = Date.parse(claim.renewed_at ?? claim.claimed_at);
38
+ return now.getTime() - last >= timeoutMinutes * 60_000;
39
+ }
40
+ function isClaim(v) {
41
+ if (typeof v !== 'object' || v === null)
42
+ return false;
43
+ const r = v;
44
+ return (typeof r.session === 'string' &&
45
+ typeof r.claimed_at === 'string' &&
46
+ !Number.isNaN(Date.parse(r.claimed_at)) &&
47
+ (r.id_source === 'platform' || r.id_source === 'agent'));
48
+ }
49
+ class CommonDirStore {
50
+ claimsDir;
51
+ projectRoot;
52
+ timeoutMinutes;
53
+ kind = 'common-dir';
54
+ constructor(claimsDir, projectRoot, timeoutMinutes) {
55
+ this.claimsDir = claimsDir;
56
+ this.projectRoot = projectRoot;
57
+ this.timeoutMinutes = timeoutMinutes;
58
+ }
59
+ claimPath(instance) {
60
+ if (!INSTANCE_NAME_RE.test(instance))
61
+ throw new Error(`invalid instance name: ${instance}`);
62
+ return join(this.claimsDir, `${instance}.yaml`);
63
+ }
64
+ instanceYamlPath(instance) {
65
+ return join(this.projectRoot, '.aidlc', 'state', instance, 'instance.yaml');
66
+ }
67
+ readFile(instance) {
68
+ try {
69
+ const data = parseYaml(readFileSync(this.claimPath(instance), 'utf8'));
70
+ return isClaim(data) ? data : null;
71
+ }
72
+ catch {
73
+ return null;
74
+ }
75
+ }
76
+ /**
77
+ * Migrate a legacy instance.yaml `claim:` into the store (AC-4, AC-23).
78
+ * Idempotent; instance.yaml ends with `claim: null` in every path.
79
+ */
80
+ migrateLegacy(instance) {
81
+ const yamlPath = this.instanceYamlPath(instance);
82
+ if (!existsSync(yamlPath))
83
+ return;
84
+ let state;
85
+ try {
86
+ state = loadYaml(yamlPath);
87
+ }
88
+ catch {
89
+ return;
90
+ }
91
+ if (!state.claim)
92
+ return;
93
+ const legacy = {
94
+ session: state.claim.session,
95
+ claimed_at: state.claim.claimed_at,
96
+ ...(state.claim.renewed_at ? { renewed_at: state.claim.renewed_at } : {}),
97
+ id_source: state.claim.session.startsWith('agent-') ? 'agent' : 'platform',
98
+ };
99
+ const existing = this.readFile(instance);
100
+ // Common-dir precedence: an existing non-stale claim wins; the legacy
101
+ // claim is discarded without a session event (its winner already logged one).
102
+ if (!(existing && !isStale(existing, this.timeoutMinutes)) && !isStale(legacy, this.timeoutMinutes)) {
103
+ this.atomicWrite(instance, legacy);
104
+ }
105
+ state.claim = null;
106
+ saveYaml(yamlPath, state);
107
+ }
108
+ atomicWrite(instance, claim) {
109
+ mkdirSync(this.claimsDir, { recursive: true });
110
+ const target = this.claimPath(instance);
111
+ const tmp = `${target}.tmp.${process.pid}`;
112
+ writeFileSync(tmp, serializeYaml(claim), 'utf8');
113
+ renameSync(tmp, target);
114
+ }
115
+ read(instance) {
116
+ this.migrateLegacy(instance);
117
+ return this.readFile(instance);
118
+ }
119
+ write(instance, claim, opts = {}) {
120
+ // read-check-write-recheck-rename (NFR-2)
121
+ const check = () => {
122
+ const current = this.read(instance);
123
+ if (current &&
124
+ current.session !== claim.session &&
125
+ !isStale(current, this.timeoutMinutes) &&
126
+ !opts.override) {
127
+ throw new ClaimConflictError(instance, current.session);
128
+ }
129
+ };
130
+ check();
131
+ mkdirSync(this.claimsDir, { recursive: true });
132
+ const target = this.claimPath(instance);
133
+ const tmp = `${target}.tmp.${process.pid}`;
134
+ writeFileSync(tmp, serializeYaml(claim), 'utf8');
135
+ try {
136
+ check();
137
+ }
138
+ catch (err) {
139
+ unlinkSync(tmp);
140
+ throw err;
141
+ }
142
+ renameSync(tmp, target);
143
+ }
144
+ release(instance) {
145
+ this.migrateLegacy(instance);
146
+ try {
147
+ unlinkSync(this.claimPath(instance));
148
+ }
149
+ catch (err) {
150
+ if (err.code !== 'ENOENT')
151
+ throw err;
152
+ }
153
+ }
154
+ list() {
155
+ const out = new Map();
156
+ let files;
157
+ try {
158
+ files = readdirSync(this.claimsDir);
159
+ }
160
+ catch {
161
+ return out;
162
+ }
163
+ for (const f of files) {
164
+ if (!f.endsWith('.yaml'))
165
+ continue;
166
+ const instance = f.slice(0, -'.yaml'.length);
167
+ const claim = this.readFile(instance);
168
+ if (claim)
169
+ out.set(instance, claim);
170
+ }
171
+ return out;
172
+ }
173
+ }
174
+ // ---------------------------------------------------------------------------
175
+ // Legacy store (non-git projects / unwritable common dir)
176
+ // ---------------------------------------------------------------------------
177
+ class LegacyStore {
178
+ projectRoot;
179
+ timeoutMinutes;
180
+ kind = 'legacy';
181
+ constructor(projectRoot, timeoutMinutes) {
182
+ this.projectRoot = projectRoot;
183
+ this.timeoutMinutes = timeoutMinutes;
184
+ }
185
+ instanceYamlPath(instance) {
186
+ return join(this.projectRoot, '.aidlc', 'state', instance, 'instance.yaml');
187
+ }
188
+ read(instance) {
189
+ let state;
190
+ try {
191
+ state = loadYaml(this.instanceYamlPath(instance));
192
+ }
193
+ catch {
194
+ return null;
195
+ }
196
+ if (!state.claim)
197
+ return null;
198
+ return {
199
+ session: state.claim.session,
200
+ claimed_at: state.claim.claimed_at,
201
+ ...(state.claim.renewed_at ? { renewed_at: state.claim.renewed_at } : {}),
202
+ id_source: state.claim.session.startsWith('agent-') ? 'agent' : 'platform',
203
+ };
204
+ }
205
+ write(instance, claim, opts = {}) {
206
+ const yamlPath = this.instanceYamlPath(instance);
207
+ const state = loadYaml(yamlPath);
208
+ if (state.claim &&
209
+ state.claim.session !== claim.session &&
210
+ !isStale(this.read(instance), this.timeoutMinutes) &&
211
+ !opts.override) {
212
+ throw new ClaimConflictError(instance, state.claim.session);
213
+ }
214
+ state.claim = {
215
+ session: claim.session,
216
+ claimed_at: claim.claimed_at,
217
+ ...(claim.renewed_at ? { renewed_at: claim.renewed_at } : {}),
218
+ };
219
+ saveYaml(yamlPath, state);
220
+ }
221
+ release(instance) {
222
+ const yamlPath = this.instanceYamlPath(instance);
223
+ let state;
224
+ try {
225
+ state = loadYaml(yamlPath);
226
+ }
227
+ catch {
228
+ return;
229
+ }
230
+ if (!state.claim)
231
+ return;
232
+ state.claim = null;
233
+ saveYaml(yamlPath, state);
234
+ }
235
+ list() {
236
+ const out = new Map();
237
+ const stateDir = join(this.projectRoot, '.aidlc', 'state');
238
+ let names;
239
+ try {
240
+ names = readdirSync(stateDir);
241
+ }
242
+ catch {
243
+ return out;
244
+ }
245
+ for (const name of names) {
246
+ const claim = this.read(name);
247
+ if (claim)
248
+ out.set(name, claim);
249
+ }
250
+ return out;
251
+ }
252
+ }
253
+ // ---------------------------------------------------------------------------
254
+ // Factory
255
+ // ---------------------------------------------------------------------------
256
+ let warned = false;
257
+ /**
258
+ * Open the claim store for a project. Common-dir backed inside a writable
259
+ * git repo; legacy instance.yaml backed otherwise (AC-5, AC-27) with a
260
+ * one-time warning.
261
+ */
262
+ export function openClaimStore(projectRoot, timeoutMinutes) {
263
+ const root = coordinationRoot(projectRoot);
264
+ if (root !== null) {
265
+ return new CommonDirStore(join(root, 'claims'), projectRoot, timeoutMinutes);
266
+ }
267
+ if (!warned) {
268
+ warned = true;
269
+ process.stderr.write('aidlc: git common dir unavailable or unwritable — claims fall back to instance.yaml\n');
270
+ }
271
+ return new LegacyStore(projectRoot, timeoutMinutes);
272
+ }
273
+ //# sourceMappingURL=claim-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claim-store.js","sourceRoot":"","sources":["../../src/concurrency/claim-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Git common-dir resolution shared by claim store and registry.
3
+ *
4
+ * All worktrees of a repository share one common dir (the primary
5
+ * checkout's `.git`); coordination data placed under
6
+ * `<common-dir>/aidlc/` is therefore visible from every worktree (AC-2).
7
+ *
8
+ * Extracted from cost/sessions.ts `repoIdentity` so both modules use one
9
+ * resolution path (design DD1).
10
+ */
11
+ /**
12
+ * Resolve the git common dir for `cwd`, or null outside a git repo (AC-5).
13
+ * Symlink-normalized (realpath) so the same dir compares equal regardless
14
+ * of the path it was reached through (e.g. macOS /var → /private/var).
15
+ */
16
+ export declare function gitCommonDir(cwd: string): string | null;
17
+ /**
18
+ * Resolve the writable coordination root `<common-dir>/aidlc/`, creating it
19
+ * on first use. Returns null outside a git repo (AC-5) or when the common
20
+ * dir is not writable (AC-27) — callers fall back to legacy behavior.
21
+ */
22
+ export declare function coordinationRoot(cwd: string): string | null;
23
+ /** True when `cwd` is inside a linked worktree (not the primary checkout). */
24
+ export declare function isLinkedWorktree(cwd: string): boolean;
25
+ //# sourceMappingURL=common-dir.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"common-dir.d.ts","sourceRoot":"","sources":["../../src/concurrency/common-dir.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAgBvD;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAW3D;AAED,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAmBrD"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Git common-dir resolution shared by claim store and registry.
3
+ *
4
+ * All worktrees of a repository share one common dir (the primary
5
+ * checkout's `.git`); coordination data placed under
6
+ * `<common-dir>/aidlc/` is therefore visible from every worktree (AC-2).
7
+ *
8
+ * Extracted from cost/sessions.ts `repoIdentity` so both modules use one
9
+ * resolution path (design DD1).
10
+ */
11
+ import { accessSync, constants, existsSync, mkdirSync, realpathSync } from 'node:fs';
12
+ import { isAbsolute, join, resolve } from 'node:path';
13
+ import { execFileSync } from 'node:child_process';
14
+ /**
15
+ * Resolve the git common dir for `cwd`, or null outside a git repo (AC-5).
16
+ * Symlink-normalized (realpath) so the same dir compares equal regardless
17
+ * of the path it was reached through (e.g. macOS /var → /private/var).
18
+ */
19
+ export function gitCommonDir(cwd) {
20
+ try {
21
+ const out = execFileSync('git', ['rev-parse', '--git-common-dir'], {
22
+ cwd,
23
+ encoding: 'utf8',
24
+ stdio: ['ignore', 'pipe', 'ignore'],
25
+ }).trim();
26
+ const abs = isAbsolute(out) ? out : resolve(cwd, out);
27
+ try {
28
+ return realpathSync(abs);
29
+ }
30
+ catch {
31
+ return abs;
32
+ }
33
+ }
34
+ catch {
35
+ return null;
36
+ }
37
+ }
38
+ /**
39
+ * Resolve the writable coordination root `<common-dir>/aidlc/`, creating it
40
+ * on first use. Returns null outside a git repo (AC-5) or when the common
41
+ * dir is not writable (AC-27) — callers fall back to legacy behavior.
42
+ */
43
+ export function coordinationRoot(cwd) {
44
+ const common = gitCommonDir(cwd);
45
+ if (common === null)
46
+ return null;
47
+ const root = join(common, 'aidlc');
48
+ try {
49
+ mkdirSync(root, { recursive: true });
50
+ accessSync(root, constants.W_OK);
51
+ return root;
52
+ }
53
+ catch {
54
+ return null;
55
+ }
56
+ }
57
+ /** True when `cwd` is inside a linked worktree (not the primary checkout). */
58
+ export function isLinkedWorktree(cwd) {
59
+ const common = gitCommonDir(cwd);
60
+ if (common === null)
61
+ return false;
62
+ try {
63
+ const gitDir = execFileSync('git', ['rev-parse', '--git-dir'], {
64
+ cwd,
65
+ encoding: 'utf8',
66
+ stdio: ['ignore', 'pipe', 'ignore'],
67
+ }).trim();
68
+ let absGitDir = isAbsolute(gitDir) ? gitDir : resolve(cwd, gitDir);
69
+ try {
70
+ absGitDir = realpathSync(absGitDir);
71
+ }
72
+ catch {
73
+ // keep resolved path
74
+ }
75
+ return absGitDir !== common && existsSync(absGitDir);
76
+ }
77
+ catch {
78
+ return false;
79
+ }
80
+ }
81
+ //# sourceMappingURL=common-dir.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"common-dir.js","sourceRoot":"","sources":["../../src/concurrency/common-dir.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACrF,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,KAAK,EAAE,CAAC,WAAW,EAAE,kBAAkB,CAAC,EAAE;YACjE,GAAG;YACH,QAAQ,EAAE,MAAM;YAChB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;SACpC,CAAC,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,GAAG,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACtD,IAAI,CAAC;YACH,OAAO,YAAY,CAAC,GAAG,CAAC,CAAC;QAC3B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,GAAG,CAAC;QACb,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACjC,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,IAAI,CAAC;QACH,SAAS,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACrC,UAAU,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;QACjC,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAClC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,EAAE,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE;YAC7D,GAAG;YACH,QAAQ,EAAE,MAAM;YAChB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;SACpC,CAAC,CAAC,IAAI,EAAE,CAAC;QACV,IAAI,SAAS,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QACnE,IAAI,CAAC;YACH,SAAS,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC;QACtC,CAAC;QAAC,MAAM,CAAC;YACP,qBAAqB;QACvB,CAAC;QACD,OAAO,SAAS,KAAK,MAAM,IAAI,UAAU,CAAC,SAAS,CAAC,CAAC;IACvD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Concurrency detection: is another session working in this repository?
3
+ * (AC-7, AC-8, AC-12, AC-13, AC-14)
4
+ *
5
+ * Primary signal: another session's non-stale claim in the claim store.
6
+ * Pure reads — one directory listing plus YAML parses, no git subprocess
7
+ * on the hot path (NFR-1). Branch-pattern / dirty-tree corroboration is
8
+ * for reporting only and never blocks alone.
9
+ *
10
+ * Own-session exclusion (design F2): claims matching the resolved session
11
+ * id are excluded, as are claims recorded from this worktree's own path.
12
+ */
13
+ import { type ClaimStore } from './claim-store.js';
14
+ import { type ConcurrencyConfig, type Detection } from './types.js';
15
+ /** Load the `concurrency:` block from `.aidlc/config.yaml` (AC-13). */
16
+ export declare function loadConcurrencyConfig(projectRoot: string): ConcurrencyConfig;
17
+ /**
18
+ * Detect other live sessions holding claims in this repository (AC-7).
19
+ * Staleness is `claim_timeout_minutes` — the one existing knob, no
20
+ * parallel threshold (idea decision 4).
21
+ */
22
+ export declare function detectConcurrency(store: ClaimStore, ownSessionId: string, timeoutMinutes: number, projectRoot: string, now?: Date): Detection;
23
+ //# sourceMappingURL=detect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../../src/concurrency/detect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,EAAW,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAuB,KAAK,iBAAiB,EAAE,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AAEzF,uEAAuE;AACvE,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,iBAAiB,CAqB5E;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,UAAU,EACjB,YAAY,EAAE,MAAM,EACpB,cAAc,EAAE,MAAM,EACtB,WAAW,EAAE,MAAM,EACnB,GAAG,GAAE,IAAiB,GACrB,SAAS,CAUX"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Concurrency detection: is another session working in this repository?
3
+ * (AC-7, AC-8, AC-12, AC-13, AC-14)
4
+ *
5
+ * Primary signal: another session's non-stale claim in the claim store.
6
+ * Pure reads — one directory listing plus YAML parses, no git subprocess
7
+ * on the hot path (NFR-1). Branch-pattern / dirty-tree corroboration is
8
+ * for reporting only and never blocks alone.
9
+ *
10
+ * Own-session exclusion (design F2): claims matching the resolved session
11
+ * id are excluded, as are claims recorded from this worktree's own path.
12
+ */
13
+ import { existsSync, readFileSync } from 'node:fs';
14
+ import { join, resolve } from 'node:path';
15
+ import { parse as parseYaml } from 'yaml';
16
+ import { isStale } from './claim-store.js';
17
+ import { DEFAULT_CONCURRENCY } from './types.js';
18
+ /** Load the `concurrency:` block from `.aidlc/config.yaml` (AC-13). */
19
+ export function loadConcurrencyConfig(projectRoot) {
20
+ const configPath = join(projectRoot, '.aidlc', 'config.yaml');
21
+ if (!existsSync(configPath))
22
+ return DEFAULT_CONCURRENCY;
23
+ try {
24
+ const config = parseYaml(readFileSync(configPath, 'utf8'));
25
+ const block = config?.concurrency;
26
+ if (!block)
27
+ return DEFAULT_CONCURRENCY;
28
+ const mode = block.mode === 'forbid' || block.mode === 'ask' || block.mode === 'offer-worktree'
29
+ ? block.mode
30
+ : DEFAULT_CONCURRENCY.mode;
31
+ const worktree_dir = typeof block.worktree_dir === 'string' && block.worktree_dir.trim() !== ''
32
+ ? block.worktree_dir
33
+ : DEFAULT_CONCURRENCY.worktree_dir;
34
+ return { mode, worktree_dir };
35
+ }
36
+ catch {
37
+ return DEFAULT_CONCURRENCY;
38
+ }
39
+ }
40
+ /**
41
+ * Detect other live sessions holding claims in this repository (AC-7).
42
+ * Staleness is `claim_timeout_minutes` — the one existing knob, no
43
+ * parallel threshold (idea decision 4).
44
+ */
45
+ export function detectConcurrency(store, ownSessionId, timeoutMinutes, projectRoot, now = new Date()) {
46
+ const holders = [];
47
+ const here = resolve(projectRoot);
48
+ for (const [instance, claim] of store.list()) {
49
+ if (claim.session === ownSessionId)
50
+ continue;
51
+ if (claim.worktree_path && resolve(claim.worktree_path) === here)
52
+ continue;
53
+ if (isStale(claim, timeoutMinutes, now))
54
+ continue;
55
+ holders.push({ instance, session: claim.session });
56
+ }
57
+ return { busy: holders.length > 0, holders };
58
+ }
59
+ //# sourceMappingURL=detect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect.js","sourceRoot":"","sources":["../../src/concurrency/detect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAmB,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAA0C,MAAM,YAAY,CAAC;AAEzF,uEAAuE;AACvE,MAAM,UAAU,qBAAqB,CAAC,WAAmB;IACvD,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC;IAC9D,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,mBAAmB,CAAC;IACxD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,SAAS,CAAC,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAEjD,CAAC;QACT,MAAM,KAAK,GAAG,MAAM,EAAE,WAAW,CAAC;QAClC,IAAI,CAAC,KAAK;YAAE,OAAO,mBAAmB,CAAC;QACvC,MAAM,IAAI,GACR,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,KAAK,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,gBAAgB;YAChF,CAAC,CAAC,KAAK,CAAC,IAAI;YACZ,CAAC,CAAC,mBAAmB,CAAC,IAAI,CAAC;QAC/B,MAAM,YAAY,GAChB,OAAO,KAAK,CAAC,YAAY,KAAK,QAAQ,IAAI,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,EAAE;YACxE,CAAC,CAAC,KAAK,CAAC,YAAY;YACpB,CAAC,CAAC,mBAAmB,CAAC,YAAY,CAAC;QACvC,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,mBAAmB,CAAC;IAC7B,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAC/B,KAAiB,EACjB,YAAoB,EACpB,cAAsB,EACtB,WAAmB,EACnB,MAAY,IAAI,IAAI,EAAE;IAEtB,MAAM,OAAO,GAAyB,EAAE,CAAC;IACzC,MAAM,IAAI,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAClC,KAAK,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;QAC7C,IAAI,KAAK,CAAC,OAAO,KAAK,YAAY;YAAE,SAAS;QAC7C,IAAI,KAAK,CAAC,aAAa,IAAI,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,KAAK,IAAI;YAAE,SAAS;QAC3E,IAAI,OAAO,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,CAAC;YAAE,SAAS;QAClD,OAAO,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IACrD,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,OAAO,EAAE,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * .gitattributes installer for AIDLC NDJSON merge hygiene (AC-19).
3
+ *
4
+ * merge=union lets parallel branches append to the same session/cost/
5
+ * transition logs and merge without conflict. Duplicated lines from
6
+ * same-position appends are handled at read time by whole-line dedup
7
+ * (AC-20, design F7).
8
+ *
9
+ * Idempotent: exact-line match before append, never duplicates entries.
10
+ */
11
+ export declare const MERGE_UNION_LINES: string[];
12
+ /**
13
+ * Ensure the merge=union entries exist in the repo-root .gitattributes.
14
+ * Returns the lines actually added (empty when already installed).
15
+ */
16
+ export declare function installGitattributes(projectRoot: string): string[];
17
+ /**
18
+ * Whole-line NDJSON dedup (AC-20): merge=union duplicates are
19
+ * byte-identical, while distinct same-second events differ somewhere in
20
+ * the line — so line identity never false-dedups (design F7).
21
+ */
22
+ export declare function dedupLines(lines: string[]): string[];
23
+ //# sourceMappingURL=gitattributes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitattributes.d.ts","sourceRoot":"","sources":["../../src/concurrency/gitattributes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,eAAO,MAAM,iBAAiB,UAI7B,CAAC;AAEF;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAqBlE;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CASpD"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * .gitattributes installer for AIDLC NDJSON merge hygiene (AC-19).
3
+ *
4
+ * merge=union lets parallel branches append to the same session/cost/
5
+ * transition logs and merge without conflict. Duplicated lines from
6
+ * same-position appends are handled at read time by whole-line dedup
7
+ * (AC-20, design F7).
8
+ *
9
+ * Idempotent: exact-line match before append, never duplicates entries.
10
+ */
11
+ import { existsSync, readFileSync, appendFileSync } from 'node:fs';
12
+ import { join } from 'node:path';
13
+ export const MERGE_UNION_LINES = [
14
+ '.aidlc/state/**/sessions.ndjson merge=union',
15
+ '.aidlc/state/**/transitions.log merge=union',
16
+ '.aidlc/state/**/costs.ndjson merge=union',
17
+ ];
18
+ /**
19
+ * Ensure the merge=union entries exist in the repo-root .gitattributes.
20
+ * Returns the lines actually added (empty when already installed).
21
+ */
22
+ export function installGitattributes(projectRoot) {
23
+ const path = join(projectRoot, '.gitattributes');
24
+ const existing = existsSync(path)
25
+ ? new Set(readFileSync(path, 'utf8')
26
+ .split('\n')
27
+ .map((l) => l.trim()))
28
+ : new Set();
29
+ const missing = MERGE_UNION_LINES.filter((l) => !existing.has(l));
30
+ if (missing.length === 0)
31
+ return [];
32
+ const prefix = existsSync(path) && !readFileSync(path, 'utf8').endsWith('\n') ? '\n' : '';
33
+ appendFileSync(path, `${prefix}# AIDLC append-only logs merge cleanly across worktree branches\n${missing.join('\n')}\n`, 'utf8');
34
+ return missing;
35
+ }
36
+ /**
37
+ * Whole-line NDJSON dedup (AC-20): merge=union duplicates are
38
+ * byte-identical, while distinct same-second events differ somewhere in
39
+ * the line — so line identity never false-dedups (design F7).
40
+ */
41
+ export function dedupLines(lines) {
42
+ const seen = new Set();
43
+ const out = [];
44
+ for (const line of lines) {
45
+ if (seen.has(line))
46
+ continue;
47
+ seen.add(line);
48
+ out.push(line);
49
+ }
50
+ return out;
51
+ }
52
+ //# sourceMappingURL=gitattributes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitattributes.js","sourceRoot":"","sources":["../../src/concurrency/gitattributes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACnE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,6CAA6C;IAC7C,6CAA6C;IAC7C,0CAA0C;CAC3C,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,WAAmB;IACtD,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,gBAAgB,CAAC,CAAC;IACjD,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC;QAC/B,CAAC,CAAC,IAAI,GAAG,CACL,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC;aACvB,KAAK,CAAC,IAAI,CAAC;aACX,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CACxB;QACH,CAAC,CAAC,IAAI,GAAG,EAAU,CAAC;IAEtB,MAAM,OAAO,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAClE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEpC,MAAM,MAAM,GACV,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7E,cAAc,CACZ,IAAI,EACJ,GAAG,MAAM,oEAAoE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EACnG,MAAM,CACP,CAAC;IACF,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,KAAe;IACxC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAS;QAC7B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACf,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Instance registry: cross-worktree instance existence (AC-15, AC-16,
3
+ * AC-24, AC-26).
4
+ *
5
+ * One YAML file per instance under `<common-dir>/aidlc/registry/`.
6
+ * Creation writes with flag 'wx' DIRECTLY on the final path — the atomic
7
+ * exactly-one-success arbiter for racing `aidlc start` calls (AC-24).
8
+ * This is the one deliberate exception to the temp-rename discipline:
9
+ * creation is a single write, and readers tolerate parse failures as
10
+ * absent, so there is no partial-read hazard.
11
+ *
12
+ * The registry is live coordination data, not durable identity (design
13
+ * F5): entries exist from creation until pruned or completed. A merged
14
+ * instance with no entry and no claim is "complete and inactive".
15
+ */
16
+ import type { RegistryEntry } from './types.js';
17
+ /** Thrown when a registry entry for the name already exists (AC-24). */
18
+ export declare class RegistryConflictError extends Error {
19
+ readonly instance: string;
20
+ constructor(instance: string);
21
+ }
22
+ export interface PruneItem {
23
+ name: string;
24
+ reason: string;
25
+ }
26
+ export declare class Registry {
27
+ private readonly dir;
28
+ private readonly projectRoot;
29
+ constructor(dir: string, projectRoot: string);
30
+ private entryPath;
31
+ /** Register a new instance; fails atomically if it exists (AC-24). */
32
+ register(entry: RegistryEntry): void;
33
+ lookup(name: string): RegistryEntry | null;
34
+ list(): RegistryEntry[];
35
+ /** Remove an entry (AC-17 completion cleanup). Missing entry is a no-op. */
36
+ remove(name: string): void;
37
+ /** True when `branch` is merged into main (AC-26). */
38
+ private isMerged;
39
+ private branchExists;
40
+ /**
41
+ * Identify stale entries: worktree path gone AND branch merged or
42
+ * deleted (AC-16). Removal only happens here — explicit prune.
43
+ */
44
+ prune(): PruneItem[];
45
+ }
46
+ /** Open the registry, or null when no writable common dir exists (AC-27). */
47
+ export declare function openRegistry(projectRoot: string): Registry | null;
48
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/concurrency/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAeH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAIhD,wEAAwE;AACxE,qBAAa,qBAAsB,SAAQ,KAAK;aAClB,QAAQ,EAAE,MAAM;gBAAhB,QAAQ,EAAE,MAAM;CAI7C;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAaD,qBAAa,QAAQ;IAEjB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,WAAW;gBADX,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,MAAM;IAGtC,OAAO,CAAC,SAAS;IAKjB,sEAAsE;IACtE,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI;IAepC,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI;IAS1C,IAAI,IAAI,aAAa,EAAE;IAgBvB,4EAA4E;IAC5E,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAQ1B,sDAAsD;IACtD,OAAO,CAAC,QAAQ;IAYhB,OAAO,CAAC,YAAY;IAYpB;;;OAGG;IACH,KAAK,IAAI,SAAS,EAAE;CAkBrB;AAED,6EAA6E;AAC7E,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAIjE"}