@adhd/backlog 0.1.6 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/client.d.ts +25 -1
- package/index.js +97 -97
- package/index.mjs +4479 -4308
- package/model.d.ts +68 -0
- package/package.json +2 -2
- package/store/lifecycle.d.ts +13 -0
- package/store/repo-migration.d.ts +51 -0
- package/store/signal-cleanup.d.ts +38 -0
package/model.d.ts
CHANGED
|
@@ -145,6 +145,16 @@ export interface CreateItemInput {
|
|
|
145
145
|
dedupeScan?: DedupeScanInput;
|
|
146
146
|
/** Skip the dedupe gate and file anyway (planner override after reviewing candidates). */
|
|
147
147
|
force?: boolean;
|
|
148
|
+
/**
|
|
149
|
+
* Citations to attach at creation time (BUG-BACKLOG-CREATE-ITEM-DROPS-CITATIONS-001).
|
|
150
|
+
* Previously absent from this interface entirely — a caller passing
|
|
151
|
+
* `citations` on create got a success response with the item created and
|
|
152
|
+
* the citations silently discarded (no such field existed to carry them
|
|
153
|
+
* through). Validated the same way as every other citation write path
|
|
154
|
+
* (`Citation.file` non-empty — see `lifecycle.ts`'s `assertValidCitation`)
|
|
155
|
+
* and rejected as a whole (no partial write) before allocation runs.
|
|
156
|
+
*/
|
|
157
|
+
citations?: Citation[];
|
|
148
158
|
}
|
|
149
159
|
export interface CreateItemResult {
|
|
150
160
|
item: BacklogItem;
|
|
@@ -367,3 +377,61 @@ export interface MigrationStatusResult {
|
|
|
367
377
|
export interface SetMigrationPhaseResult extends MigrationStatusResult {
|
|
368
378
|
configPath: string;
|
|
369
379
|
}
|
|
380
|
+
/** One item's move within a `RepoMigrationPlan` — always dry-runnable, never mutates on its own. */
|
|
381
|
+
export interface RepoMigrationPlanItem {
|
|
382
|
+
nodeId: number;
|
|
383
|
+
/** The humanId this item currently carries in `fromRepo`. */
|
|
384
|
+
humanId: string;
|
|
385
|
+
/**
|
|
386
|
+
* The humanId this item will carry in `toRepo` — identical to `humanId`
|
|
387
|
+
* unless `renamed` is true. Deterministic: preserves the item's `family`
|
|
388
|
+
* prefix and picks the next free number in that family within `toRepo`
|
|
389
|
+
* (mirrors `computeNextHumanId`'s own `max + 1` allocation rule), scanning
|
|
390
|
+
* BOTH `toRepo`'s pre-existing items AND every earlier item in this same
|
|
391
|
+
* plan already assigned a number in that family — so two colliding items
|
|
392
|
+
* sharing a family (e.g. two different `BUG-001`s) never collide with each
|
|
393
|
+
* other's rename target either.
|
|
394
|
+
*/
|
|
395
|
+
targetHumanId: string;
|
|
396
|
+
/** True iff `humanId` already exists as a LIVE item in `toRepo` and had to be renamed to avoid an id collision. */
|
|
397
|
+
renamed: boolean;
|
|
398
|
+
title: string;
|
|
399
|
+
status: BacklogStatus;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* The full, deterministic plan for moving every live item out of `fromRepo`
|
|
403
|
+
* into `toRepo` — computed by a pure read-only scan (`planRepoMigration`),
|
|
404
|
+
* safe to call repeatedly and to inspect before ever mutating anything.
|
|
405
|
+
*/
|
|
406
|
+
export interface RepoMigrationPlan {
|
|
407
|
+
fromRepo: string;
|
|
408
|
+
toRepo: string;
|
|
409
|
+
items: RepoMigrationPlanItem[];
|
|
410
|
+
/** Count of `items` where `renamed === true` — the collision count. */
|
|
411
|
+
collisionCount: number;
|
|
412
|
+
}
|
|
413
|
+
/** Per-item outcome of actually executing a `RepoMigrationPlan`. Every planned item gets exactly one of these — nothing is ever silently dropped. */
|
|
414
|
+
export interface RepoMigrationItemResult {
|
|
415
|
+
nodeId: number;
|
|
416
|
+
fromHumanId: string;
|
|
417
|
+
toHumanId: string;
|
|
418
|
+
renamed: boolean;
|
|
419
|
+
ok: boolean;
|
|
420
|
+
/** Present iff `ok === false` — the item was left untouched in `fromRepo`, never partially moved. */
|
|
421
|
+
error?: string;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* `migrateRepo`'s result. When `dryRun` is true (the default — a caller must
|
|
425
|
+
* pass `dryRun:false` explicitly to mutate anything), `results` is absent and
|
|
426
|
+
* NOTHING was written; `plan` alone previews exactly what would happen.
|
|
427
|
+
*/
|
|
428
|
+
export interface RepoMigrationResult {
|
|
429
|
+
fromRepo: string;
|
|
430
|
+
toRepo: string;
|
|
431
|
+
dryRun: boolean;
|
|
432
|
+
plan: RepoMigrationPlan;
|
|
433
|
+
/** Present only when `dryRun === false`. One entry per `plan.items` entry, same order. */
|
|
434
|
+
results?: RepoMigrationItemResult[];
|
|
435
|
+
succeeded?: number;
|
|
436
|
+
failed?: number;
|
|
437
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhd/backlog",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"bin": {
|
|
5
5
|
"backlog": "index.js"
|
|
6
6
|
},
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"@adhd/apigen-plugin-ir-cache": "^0.1.0",
|
|
14
14
|
"@adhd/apigen-plugin-mcp": "^0.2.3",
|
|
15
15
|
"@adhd/apigen-plugin-openapi": "^0.2.2",
|
|
16
|
-
"@adhd/environment": "^0.1.
|
|
16
|
+
"@adhd/environment": "^0.1.5",
|
|
17
17
|
"@adhd/environment-base-spec": "^0.1.0",
|
|
18
18
|
"@adhd/sox-graph-store": "^0.8.4",
|
|
19
19
|
"@adhd/sox-store-adapter": "^0.5.8",
|
package/store/lifecycle.d.ts
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import { GraphBacklogStore } from './graph-backlog-store.js';
|
|
2
2
|
import { ArchiveOpts, BacklogItem, BacklogStatus, Citation, StatsScope, TransitionOpts } from '../model.js';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* `Citation.file` is required at the TypeScript/JSON-Schema level
|
|
6
|
+
* (BUG-APIGEN-CORE-CLIENT-001 makes that presence check reach the extracted
|
|
7
|
+
* schema), but presence alone still accepts `""`/whitespace — a citation
|
|
8
|
+
* with no actual file is not evidence. Every write path that accepts a
|
|
9
|
+
* caller-supplied `Citation` (inline on `transitionStatus`/`resolveItem`, and
|
|
10
|
+
* standalone `addCitation`) runs every entry through this before it is
|
|
11
|
+
* persisted. Also reused by `crud.ts`'s `createItemNode` and
|
|
12
|
+
* `structure.ts`'s `supersedeItemNode` for citations supplied inline on
|
|
13
|
+
* `CreateItemInput` (BUG-BACKLOG-CREATE-ITEM-DROPS-CITATIONS-001) — one
|
|
14
|
+
* validation rule, every write path.
|
|
15
|
+
*/
|
|
16
|
+
export declare function assertValidCitation(citation: Citation): void;
|
|
4
17
|
export declare function transitionStatusNode(store: GraphBacklogStore, repo: string, humanId: string, status: BacklogStatus, opts: TransitionOpts): Promise<BacklogItem>;
|
|
5
18
|
/** Sugar for transitionStatus into any terminal status (SPEC.md §5.4). */
|
|
6
19
|
export declare function resolveItemNode(store: GraphBacklogStore, repo: string, humanId: string, status: BacklogStatus, opts: TransitionOpts): Promise<BacklogItem>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { GraphBacklogStore } from './graph-backlog-store.js';
|
|
2
|
+
import { BacklogItem, RepoMigrationItemResult, RepoMigrationPlan, RepoMigrationPlanItem, RepoMigrationResult } from '../model.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Pure, read-only, deterministic migration plan for every LIVE item whose
|
|
6
|
+
* `namespace` is `fromRepo` — deliberately namespace-scoped (not
|
|
7
|
+
* `metadata.repo`-scoped) so an item whose two repo fields have already
|
|
8
|
+
* diverged (found empirically: `FEAT-001`/`FEAT-002`, `namespace:"adhd"` but
|
|
9
|
+
* `metadata.repo:"PseudoSky/adhd"`) is still picked up and fully repaired,
|
|
10
|
+
* not silently skipped because its rendered `repo` already "looks" correct.
|
|
11
|
+
*
|
|
12
|
+
* Collision resolution walks `sourceItems` in humanId order (stable,
|
|
13
|
+
* reproducible across repeated calls) and, for a colliding humanId, assigns
|
|
14
|
+
* the next free number in that family — tracked in an in-memory
|
|
15
|
+
* `familyMaxInTarget` map seeded from `toRepo`'s REAL current max per family
|
|
16
|
+
* and incremented for every rename already planned earlier in this same
|
|
17
|
+
* pass, so two same-family collisions in one batch never target each other.
|
|
18
|
+
*/
|
|
19
|
+
export declare function planRepoMigration(store: GraphBacklogStore, fromRepo: string, toRepo: string): Promise<RepoMigrationPlan>;
|
|
20
|
+
/**
|
|
21
|
+
* Executes exactly ONE planned move, re-verifying the live state at the
|
|
22
|
+
* exact node still matches what the plan assumed (a stale plan — the source
|
|
23
|
+
* item moved/mutated, or the target humanId got claimed — since planning
|
|
24
|
+
* throws `InvalidArgumentError` rather than silently proceeding on bad
|
|
25
|
+
* assumptions; `executeRepoMigration` catches this per-item so one stale
|
|
26
|
+
* entry never aborts the whole batch or gets silently skipped).
|
|
27
|
+
*
|
|
28
|
+
* Mirrors `structure.ts`'s `renameHumanIdNode` exactly, generalized to also
|
|
29
|
+
* rewrite `namespace` (which `renameHumanIdNode` deliberately refuses to
|
|
30
|
+
* touch — same-repo only) — see this module's top-of-file doc comment for
|
|
31
|
+
* why `touch()` cannot do that column and a raw `UPDATE` is required.
|
|
32
|
+
*/
|
|
33
|
+
export declare function migrateRepoItemNode(store: GraphBacklogStore, item: RepoMigrationPlanItem, fromRepo: string, toRepo: string, actor: string): Promise<BacklogItem>;
|
|
34
|
+
/**
|
|
35
|
+
* Executes every item in `plan`, sequentially (each rename must observe the
|
|
36
|
+
* previous item's real committed state before computing/verifying the next
|
|
37
|
+
* — see `planRepoMigration`'s doc comment on why this must not run in
|
|
38
|
+
* parallel). Every planned item gets exactly one result — `ok:true` or
|
|
39
|
+
* `ok:false` with `error` — so a failure is always visible and never
|
|
40
|
+
* silently drops an item from the report; a per-item failure does not abort
|
|
41
|
+
* the remaining items (an aborted batch would leave an unpredictable subset
|
|
42
|
+
* moved with no way to tell which from the caller's plan alone).
|
|
43
|
+
*/
|
|
44
|
+
export declare function executeRepoMigration(store: GraphBacklogStore, plan: RepoMigrationPlan, actor: string): Promise<RepoMigrationItemResult[]>;
|
|
45
|
+
/**
|
|
46
|
+
* The single entry point client.ts/CLI/MCP expose. `dryRun` defaults to
|
|
47
|
+
* `true` — a caller MUST pass `dryRun:false` explicitly to mutate anything,
|
|
48
|
+
* so a bare "preview this migration" call (e.g. an agent double-checking
|
|
49
|
+
* before committing) can never accidentally execute.
|
|
50
|
+
*/
|
|
51
|
+
export declare function migrateRepo(store: GraphBacklogStore, fromRepo: string, toRepo: string, actor: string, dryRun?: boolean): Promise<RepoMigrationResult>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export interface SignalCleanupHandle {
|
|
2
|
+
/** Removes the listeners this call installed (idempotent). Call from the
|
|
3
|
+
* owning function's own `finally` once its normal-path cleanup has run,
|
|
4
|
+
* so a signal arriving AFTER that point is not double-handled. */
|
|
5
|
+
dispose: () => void;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Installs SIGINT/SIGTERM handlers that run `cleanup()` at most once, then
|
|
9
|
+
* exit with the conventional 128+signum code. `cleanup` is expected to be
|
|
10
|
+
* `closeGraphBacklogStoreSafe`-shaped (never throws) but is wrapped so a
|
|
11
|
+
* defect there can never suppress the exit or hang the process.
|
|
12
|
+
*
|
|
13
|
+
* @param cleanup Releases this connection's own store lease. Called with no
|
|
14
|
+
* arguments; capture whatever store handle it needs via closure — this
|
|
15
|
+
* lets callers install the handler BEFORE the store finishes opening
|
|
16
|
+
* (`cleanup` reads a `let store` that may still be `undefined` at signal
|
|
17
|
+
* time, in which case the underlying `closeGraphBacklogStoreSafe(undefined)`
|
|
18
|
+
* is a documented no-op).
|
|
19
|
+
* @param onExit Test-only override for `process.exit` (never overridden in
|
|
20
|
+
* production) so a red→green test can observe "would have exited with N"
|
|
21
|
+
* without actually terminating the test runner's process.
|
|
22
|
+
*/
|
|
23
|
+
export declare function installSignalCleanup(cleanup: () => Promise<void>, onExit?: (code: number) => void): SignalCleanupHandle;
|
|
24
|
+
/**
|
|
25
|
+
* True iff nothing on this process already listens for SIGINT/SIGTERM.
|
|
26
|
+
* `startBacklogServer` uses this to avoid installing a SECOND, competing
|
|
27
|
+
* handler when its caller (`serve.ts`'s `runServeCommand`) has already
|
|
28
|
+
* registered its own SIGINT/SIGTERM → `AbortController.abort()` handling
|
|
29
|
+
* BEFORE ever calling into this function — that existing handler already
|
|
30
|
+
* overrides Node's default terminate-without-cleanup behaviour for the
|
|
31
|
+
* entire async store-open window, and it drives a *graceful* drain (fastify/
|
|
32
|
+
* MCP transport teardown via the aborted `signal`) that a second,
|
|
33
|
+
* unconditional close+`process.exit` handler would race and potentially cut
|
|
34
|
+
* short. Direct/embedded callers of `startBacklogServer` that do NOT go
|
|
35
|
+
* through `serve.ts` have no such external coverage, so they get this
|
|
36
|
+
* module's own handler instead.
|
|
37
|
+
*/
|
|
38
|
+
export declare function hasExternalSignalHandling(): boolean;
|