gcf-aif-ext 0.7.3 → 0.7.5
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 +1 -1
- package/extension.json +1 -1
- package/lib/board.js +27 -2
- package/lib/bundle.js +9 -1
- package/lib/ledger.js +4 -2
- package/lib/session-mark.js +8 -0
- package/lib/work-ops.js +92 -31
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ On Windows, run `extension add` and `extension update` from PowerShell or cmd, n
|
|
|
52
52
|
|
|
53
53
|
### 0.7.1
|
|
54
54
|
|
|
55
|
-
- `gcf board --json`: a ready item's worktree with no run lock and no session mark is a run that stopped (a timeout, a stop, a crash), not a run: the item reads `running: false`, `resumable: true`, and keeps its moves; its next run resumes the worktree.
|
|
55
|
+
- `gcf board --json`: a ready item's worktree with no run lock and no session mark is a run that stopped (a timeout, a stop, a crash), not a run: the item reads `running: false`, `resumable: true`, and keeps its moves; its next run resumes the worktree. An agent session the harness kept (`.gcf-session/runs/<id>.agent.json`) makes a ready item `resumable` too, in either run mode, and shows as `resume: {ended, resets?}` (null without one).
|
|
56
56
|
|
|
57
57
|
### 0.7.0
|
|
58
58
|
|
package/extension.json
CHANGED
package/lib/board.js
CHANGED
|
@@ -120,6 +120,25 @@ function logOf(projectDir, id) {
|
|
|
120
120
|
return existsSync(path.join(projectDir, log)) ? log : null;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
/**
|
|
124
|
+
* How the agent session of the item's last run ended, when the harness kept it for a rerun to
|
|
125
|
+
* resume (`<id>.agent.json`): `{ended, resets?}`, or null. A file that is not a record is none.
|
|
126
|
+
*/
|
|
127
|
+
async function resumeOf(projectDir, id) {
|
|
128
|
+
try {
|
|
129
|
+
const file = path.join(projectDir, '.gcf-session', 'runs', `${id}.agent.json`);
|
|
130
|
+
const rec = JSON.parse(await readFile(file, 'utf8'));
|
|
131
|
+
if (typeof rec?.session_id !== 'string') return null;
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
ended: String(rec.ended ?? ''),
|
|
135
|
+
...(rec.resets ? { resets: String(rec.resets) } : {}),
|
|
136
|
+
};
|
|
137
|
+
} catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
123
142
|
// Fields of the item file a board shows as they are: null or [] when the file has none.
|
|
124
143
|
const SCALARS = [
|
|
125
144
|
'title',
|
|
@@ -163,10 +182,15 @@ async function itemOf(projectDir, c, verified) {
|
|
|
163
182
|
const ready = c.data.status === 'ready' && verified.has(id);
|
|
164
183
|
// `running` and `verify` are not stored (§4): a live harness run lock or a fresh session mark
|
|
165
184
|
// is a run; a trailer is a passed DoD. A worktree with neither is a run that stopped (a
|
|
166
|
-
// timeout, a stop, a crash): the item is ready, and its next run resumes that worktree.
|
|
185
|
+
// timeout, a stop, a crash): the item is ready, and its next run resumes that worktree. A
|
|
186
|
+
// session the harness kept (`resume`) is resumed by the next run in either mode.
|
|
167
187
|
const running = runHolder(projectDir, id) !== null || sessionActive(projectDir, id);
|
|
188
|
+
const resume = c.data.status === 'ready' ? await resumeOf(projectDir, id) : null;
|
|
168
189
|
const resumable =
|
|
169
|
-
!running &&
|
|
190
|
+
!running &&
|
|
191
|
+
c.data.status === 'ready' &&
|
|
192
|
+
!ready &&
|
|
193
|
+
(resume !== null || existsSync(worktreeOf(projectDir, id)));
|
|
170
194
|
|
|
171
195
|
return {
|
|
172
196
|
id,
|
|
@@ -179,6 +203,7 @@ async function itemOf(projectDir, c, verified) {
|
|
|
179
203
|
...(c.data.blocked ? { blocked: c.data.blocked } : {}),
|
|
180
204
|
running,
|
|
181
205
|
resumable,
|
|
206
|
+
resume,
|
|
182
207
|
verified: ready,
|
|
183
208
|
// What a board may offer (§4.1): nothing while a run drives the item.
|
|
184
209
|
moves: running ? [] : movesOf(c.data),
|
package/lib/bundle.js
CHANGED
|
@@ -180,13 +180,21 @@ function plan(bundle, cur, ledger) {
|
|
|
180
180
|
const writes = [];
|
|
181
181
|
const note = `lines outside any part: ${bundle.stray.join(', ')} (a lost gcf:part marker?)`;
|
|
182
182
|
const report = bundle.stray.length ? [{ part: 'bundle', outcome: 'invalid', note }] : [];
|
|
183
|
+
const keyOf = (p) => `${p.kind}:${p.name}`;
|
|
184
|
+
const keys = bundle.parts.map(keyOf);
|
|
183
185
|
for (const p of bundle.parts) {
|
|
184
|
-
const key =
|
|
186
|
+
const key = keyOf(p);
|
|
185
187
|
if (!validName(p)) {
|
|
186
188
|
report.push({ part: key, outcome: 'invalid', note: 'not a gcf doc, system or unit name' });
|
|
187
189
|
continue;
|
|
188
190
|
}
|
|
189
191
|
|
|
192
|
+
// Which copy is the designer's edit is not ours to guess: the later one would win silently.
|
|
193
|
+
if (keys.indexOf(key) !== keys.lastIndexOf(key)) {
|
|
194
|
+
report.push({ part: key, outcome: 'invalid', note: 'the bundle holds this part twice' });
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
|
|
190
198
|
const v = verdict(p, cur.get(key) ?? null);
|
|
191
199
|
if (p.kind === 'unit' && v === 'new' && ledger.known(p.name)) {
|
|
192
200
|
report.push({ part: key, outcome: 'taken', note: 'the id is used: pick another slug' });
|
package/lib/ledger.js
CHANGED
|
@@ -71,9 +71,11 @@ export async function buildLedger(projectDir) {
|
|
|
71
71
|
for (const d of design) for (const u of d.units) add(u.id, { state: 'design', file: d.file });
|
|
72
72
|
for (const c of items) add(String(c.data.id), { state: 'item', file: `work/${c.name}` });
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
// A list, not a set: two equal lines are two ends too (M12), a merge of branches that each
|
|
75
|
+
// ended the id the same way.
|
|
76
|
+
/** @type {Map<string, string[]>} */
|
|
75
77
|
const terminal = new Map();
|
|
76
|
-
const mark = (id, event) => terminal.set(id, (terminal.get(id) ??
|
|
78
|
+
const mark = (id, event) => terminal.set(id, [...(terminal.get(id) ?? []), event]);
|
|
77
79
|
for (const [id, event] of await recordLines(root, 'history.md'))
|
|
78
80
|
if (HISTORY_EVENTS.includes(event)) mark(id, event);
|
|
79
81
|
for (const [id] of await recordLines(root, 'graveyard.md')) mark(id, 'killed');
|
package/lib/session-mark.js
CHANGED
|
@@ -40,6 +40,14 @@ export function releaseSession(projectDir, id) {
|
|
|
40
40
|
rmSync(markOf(projectDir, id), { force: true });
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Forget the agent session the harness kept for a rerun of `id` (`<id>.agent.json`): a move,
|
|
45
|
+
* by hand or by an agent, ends the work it would resume.
|
|
46
|
+
*/
|
|
47
|
+
export function dropAgentSession(projectDir, id) {
|
|
48
|
+
rmSync(path.join(path.dirname(markOf(projectDir, id)), `${id}.agent.json`), { force: true });
|
|
49
|
+
}
|
|
50
|
+
|
|
43
51
|
/** Whether a session works the item now: a mark refreshed within SESSION_TTL_MS. */
|
|
44
52
|
export function sessionActive(projectDir, id) {
|
|
45
53
|
const file = markOf(projectDir, id);
|
package/lib/work-ops.js
CHANGED
|
@@ -11,7 +11,7 @@ import path from 'node:path';
|
|
|
11
11
|
import { parse, serialize } from './frontmatter.js';
|
|
12
12
|
import { buildLedger } from './ledger.js';
|
|
13
13
|
import { readConfig } from './gcf-paths.js';
|
|
14
|
-
import { atomicWrite } from './safe-path.js';
|
|
14
|
+
import { atomicWrite, resolveInside } from './safe-path.js';
|
|
15
15
|
import { allowedFrom, decisionFits, FEATURE_ONLY, fromOf } from './transitions.js';
|
|
16
16
|
import { ITEM_TYPES, DEFAULT_GATE, GATES, SEVERITIES, UNIT_ITEM_TYPES } from './validate-item.js';
|
|
17
17
|
import { isValidId } from './id.js';
|
|
@@ -37,8 +37,8 @@ import {
|
|
|
37
37
|
} from './live.js';
|
|
38
38
|
import { dropInbox } from './inbox.js';
|
|
39
39
|
import { refuseByPhase } from './phase.js';
|
|
40
|
-
import { runHolder } from './main-lock.js';
|
|
41
|
-
import { releaseSession, sessionActive } from './session-mark.js';
|
|
40
|
+
import { runHolder, withMainLock } from './main-lock.js';
|
|
41
|
+
import { dropAgentSession, releaseSession, sessionActive } from './session-mark.js';
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
44
|
* Options of a committed move; each move reads the ones it needs.
|
|
@@ -160,14 +160,13 @@ function unfinishedChildren(ledger, feature) {
|
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
/**
|
|
163
|
-
*
|
|
164
|
-
*
|
|
163
|
+
* Refuse a move on item `id` while a harness run or another agent session works it. `self` is
|
|
164
|
+
* the item this session drives itself (null: none).
|
|
165
|
+
* @param {string} projectDir
|
|
166
|
+
* @param {string} id
|
|
167
|
+
* @param {string | null} [self]
|
|
165
168
|
*/
|
|
166
|
-
|
|
167
|
-
const root = gcfOf(projectDir);
|
|
168
|
-
const ledger = await buildLedger(projectDir);
|
|
169
|
-
const item = itemOf(ledger, id);
|
|
170
|
-
need(move, item);
|
|
169
|
+
function refuseRunning(projectDir, id, self = process.env.GCF_RUN_ITEM ?? null) {
|
|
171
170
|
const run = runHolder(projectDir, id);
|
|
172
171
|
if (run)
|
|
173
172
|
throw new Error(
|
|
@@ -176,11 +175,23 @@ async function begin(projectDir, id, move) {
|
|
|
176
175
|
);
|
|
177
176
|
// The session that claimed the item moves it itself (GCF_RUN_ITEM, set by `--claimed` or by
|
|
178
177
|
// a harness run on main); anyone else waits, as for a run lock.
|
|
179
|
-
if (sessionActive(projectDir, id) &&
|
|
178
|
+
if (sessionActive(projectDir, id) && self !== id)
|
|
180
179
|
throw new Error(
|
|
181
180
|
`${id} is worked by an agent session (gcf claim): wait until it moves the item, or run ` +
|
|
182
181
|
`ai-factory gcf release ${id} when no session works it`,
|
|
183
182
|
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Open a move on item `id` from the statuses `move` allows: the files it touches, its steps,
|
|
187
|
+
* and the LIVE.md edits folded into a single write at the end.
|
|
188
|
+
*/
|
|
189
|
+
async function begin(projectDir, id, move) {
|
|
190
|
+
const root = gcfOf(projectDir);
|
|
191
|
+
const ledger = await buildLedger(projectDir);
|
|
192
|
+
const item = itemOf(ledger, id);
|
|
193
|
+
need(move, item);
|
|
194
|
+
refuseRunning(projectDir, id);
|
|
184
195
|
const files = [item.file];
|
|
185
196
|
const steps = [];
|
|
186
197
|
const liveOps = [];
|
|
@@ -217,8 +228,10 @@ async function finish(m, move, tail, commit) {
|
|
|
217
228
|
commit,
|
|
218
229
|
inputs: [m.item.file],
|
|
219
230
|
});
|
|
220
|
-
// A move on the item ends the session that worked it: the board stops showing it running
|
|
231
|
+
// A move on the item ends the session that worked it: the board stops showing it running,
|
|
232
|
+
// and no rerun resumes it.
|
|
221
233
|
releaseSession(m.projectDir, String(m.item.data.id));
|
|
234
|
+
dropAgentSession(m.projectDir, String(m.item.data.id));
|
|
222
235
|
|
|
223
236
|
return hash;
|
|
224
237
|
}
|
|
@@ -229,6 +242,16 @@ const writeItem = (item, data, body = item.body) => atomicWrite(item.file, seria
|
|
|
229
242
|
const without = (data, ...keys) =>
|
|
230
243
|
Object.fromEntries(Object.entries(data).filter(([k]) => !keys.includes(k)));
|
|
231
244
|
|
|
245
|
+
/** Refuse a move on an unplayed item edited since its close; `then` is where the edit goes. */
|
|
246
|
+
function refuseEdited(item, then) {
|
|
247
|
+
if (matchesFrozen(item)) return;
|
|
248
|
+
throw new Error(
|
|
249
|
+
`${item.data.id}: an unplayed item's body and live: are frozen, and this one was edited — ` +
|
|
250
|
+
`save the edit aside, restore the file (git restore gcf/work/${item.data.id}.md ` +
|
|
251
|
+
`discards it), make the move, then ${then}`,
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
232
255
|
/**
|
|
233
256
|
* The item's lines leave its systems docs and its added capability leaves LIVE.md. A line
|
|
234
257
|
* another unplayed item also wrote stays: that item's body still owes it to systems/.
|
|
@@ -236,12 +259,7 @@ const without = (data, ...keys) =>
|
|
|
236
259
|
function unplay(m, item) {
|
|
237
260
|
// The gate checks `frozen` only on a file that stays unplayed: a tune or a kill of an edited
|
|
238
261
|
// item would take the edited lines out and leave the frozen ones in systems/ for good.
|
|
239
|
-
|
|
240
|
-
throw new Error(
|
|
241
|
-
`${item.data.id}: an unplayed item's body and live: are frozen, and this one was edited — ` +
|
|
242
|
-
`save the edit aside, restore the file (git restore gcf/work/${item.data.id}.md ` +
|
|
243
|
-
'discards it), make the move, then apply the edit to the ready item',
|
|
244
|
-
);
|
|
262
|
+
refuseEdited(item, 'apply the edit to the ready item');
|
|
245
263
|
const systems = item.data.system ?? [];
|
|
246
264
|
m.leaving.add(String(item.data.id));
|
|
247
265
|
m.touch(...systemFiles(m.root, systems));
|
|
@@ -283,7 +301,8 @@ function tracked(projectDir, file) {
|
|
|
283
301
|
*/
|
|
284
302
|
function archivePlan(m, item) {
|
|
285
303
|
const rel = item.data.plan ?? `.ai-factory/plans/${item.data.id}.md`;
|
|
286
|
-
|
|
304
|
+
// The plan is moved and deleted: a `plan:` out of the project would take another tree's file.
|
|
305
|
+
const src = resolveInside(m.projectDir, rel);
|
|
287
306
|
if (!existsSync(src) || !tracked(m.projectDir, src)) return null;
|
|
288
307
|
const destRel = `.ai-factory/archive/plans/${path.basename(src)}`;
|
|
289
308
|
const dest = path.resolve(m.projectDir, destRel);
|
|
@@ -456,7 +475,7 @@ function refuseOpenDepends(m, move, opts = {}) {
|
|
|
456
475
|
/** ready → awaiting-verdict / awaiting-decision; `since` marks where the wait began.
|
|
457
476
|
* @param {MoveOpts} [opts]
|
|
458
477
|
*/
|
|
459
|
-
|
|
478
|
+
async function submitItemUnlocked(projectDir, id, opts = {}) {
|
|
460
479
|
const { commit = true } = opts;
|
|
461
480
|
const m = await begin(projectDir, id, 'submit');
|
|
462
481
|
if (m.item.data.gate === 'auto')
|
|
@@ -528,7 +547,7 @@ function closeFeature(m) {
|
|
|
528
547
|
* unit collapses into its systems docs and stays as unplayed; anything else ends in history.
|
|
529
548
|
* @param {MoveOpts} [opts]
|
|
530
549
|
*/
|
|
531
|
-
|
|
550
|
+
async function closeItemUnlocked(projectDir, id, opts = {}) {
|
|
532
551
|
const { constraints = [], commit = true } = opts;
|
|
533
552
|
const m = await begin(projectDir, id, 'close');
|
|
534
553
|
const unplayed = SOFT.includes(m.item.data.type);
|
|
@@ -554,6 +573,8 @@ async function keep(m, constraints) {
|
|
|
554
573
|
const { item } = m;
|
|
555
574
|
refuseOpenDepends(m, 'verdict keep');
|
|
556
575
|
if (item.data.status === 'unplayed') {
|
|
576
|
+
// systems/ and LIVE.md hold the frozen body: an edit made since would leave with the file.
|
|
577
|
+
refuseEdited(item, `carry the edit in a new item with reopens: ${item.data.id}`);
|
|
557
578
|
endToHistory(m, item, 'collapsed', { applyLive: false });
|
|
558
579
|
|
|
559
580
|
return;
|
|
@@ -599,7 +620,7 @@ async function tune(m, note) {
|
|
|
599
620
|
/** keep | tune | kill on an item waiting for its verdict or unplayed (Methodology §5.4).
|
|
600
621
|
* @param {MoveOpts} [opts]
|
|
601
622
|
*/
|
|
602
|
-
|
|
623
|
+
async function verdictItemUnlocked(projectDir, id, verdict, opts = {}) {
|
|
603
624
|
const { note = '', tags = [], remove, constraints = [], commit = true } = opts;
|
|
604
625
|
if (!['keep', 'tune', 'kill'].includes(verdict)) throw new Error(`Unknown verdict: ${verdict}`);
|
|
605
626
|
const m = await begin(projectDir, id, `verdict:${verdict}`);
|
|
@@ -617,9 +638,11 @@ export async function verdictItem(projectDir, id, verdict, opts = {}) {
|
|
|
617
638
|
function killFeature(m, { reason, remove }) {
|
|
618
639
|
const { item, ledger } = m;
|
|
619
640
|
const shipped = featureShipped(ledger, item);
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
641
|
+
const children = (item.data.children ?? []).map((c) => ledger.item(c)).filter(Boolean);
|
|
642
|
+
// A child's file goes with the feature: an agent working it would lose it mid-run.
|
|
643
|
+
// No session kills its own item through its feature: the claimed child counts as running too.
|
|
644
|
+
for (const child of children) refuseRunning(m.projectDir, String(child.data.id), null);
|
|
645
|
+
for (const child of children) {
|
|
623
646
|
endToGraveyard(m, child, {
|
|
624
647
|
reason: `feature ${item.data.id} killed: ${reason}`,
|
|
625
648
|
removal: false,
|
|
@@ -674,7 +697,7 @@ function fitDecision(choice, item) {
|
|
|
674
697
|
/** The operator's decisions (Methodology §4.1): spikes, decision items, bugs and features.
|
|
675
698
|
* @param {MoveOpts} [opts]
|
|
676
699
|
*/
|
|
677
|
-
|
|
700
|
+
async function decideItemUnlocked(projectDir, id, choice, opts = {}) {
|
|
678
701
|
const { reason = '', remove, commit = true } = opts;
|
|
679
702
|
if (!DECIDE[choice])
|
|
680
703
|
throw new Error(`Unknown decision: ${choice} (${Object.keys(DECIDE).join(' | ')})`);
|
|
@@ -689,7 +712,7 @@ export async function decideItem(projectDir, id, choice, opts = {}) {
|
|
|
689
712
|
/** ready → blocked, with the reason or the question for the operator.
|
|
690
713
|
* @param {MoveOpts} [opts]
|
|
691
714
|
*/
|
|
692
|
-
|
|
715
|
+
async function blockItemUnlocked(projectDir, id, opts = {}) {
|
|
693
716
|
const { reason, commit = true } = opts;
|
|
694
717
|
const why = String(reason ?? '').trim();
|
|
695
718
|
if (!why) throw new Error('a block needs a reason or a question');
|
|
@@ -703,7 +726,7 @@ export async function blockItem(projectDir, id, opts = {}) {
|
|
|
703
726
|
/** blocked | parked → ready; a note (an answer, a delivered asset file) goes to `notes:`.
|
|
704
727
|
* @param {MoveOpts} [opts]
|
|
705
728
|
*/
|
|
706
|
-
|
|
729
|
+
async function unblockItemUnlocked(projectDir, id, opts = {}) {
|
|
707
730
|
const { note = '', commit = true } = opts;
|
|
708
731
|
const m = await begin(projectDir, id, 'unblock');
|
|
709
732
|
if (m.item.data.status === 'parked') dropKnownIssue(m, id);
|
|
@@ -725,7 +748,7 @@ export async function unblockItem(projectDir, id, opts = {}) {
|
|
|
725
748
|
/** Any stored status → graveyard; an unplayed item leaves as a kill does.
|
|
726
749
|
* @param {MoveOpts} [opts]
|
|
727
750
|
*/
|
|
728
|
-
|
|
751
|
+
async function dropItemUnlocked(projectDir, id, opts = {}) {
|
|
729
752
|
const { reason, tags = [], remove, commit = true } = opts;
|
|
730
753
|
const m = await begin(projectDir, id, 'drop');
|
|
731
754
|
if (m.item.data.type === 'feature' && (m.item.data.children ?? []).some((c) => m.ledger.item(c)))
|
|
@@ -855,7 +878,7 @@ function newItemText(spec, gate, since) {
|
|
|
855
878
|
* Rank items in the given order: `priority` 1, 2, 3… (lower runs sooner). One commit
|
|
856
879
|
* `gcf(priority)`. A board sends a column's new order after a drag; the console names the ids.
|
|
857
880
|
*/
|
|
858
|
-
|
|
881
|
+
async function prioritizeItemsUnlocked(projectDir, ids, { commit = true } = {}) {
|
|
859
882
|
if (!ids.length) throw new Error('name the items in the order they should run');
|
|
860
883
|
if (new Set(ids).size !== ids.length) throw new Error('an id is named twice');
|
|
861
884
|
const ledger = await buildLedger(projectDir);
|
|
@@ -896,7 +919,7 @@ export async function prioritizeItems(projectDir, ids, { commit = true } = {}) {
|
|
|
896
919
|
* A new work item in ready (or blocked), no commit. A design unit enters work through `take`,
|
|
897
920
|
* not here: its body is the unit's slots. `from` erases the inbox entry it came from.
|
|
898
921
|
*/
|
|
899
|
-
|
|
922
|
+
async function newItemUnlocked(projectDir, spec) {
|
|
900
923
|
const root = gcfOf(projectDir);
|
|
901
924
|
const ledger = await buildLedger(projectDir);
|
|
902
925
|
const gate = checkNew(ledger, spec);
|
|
@@ -930,3 +953,41 @@ export async function newItem(projectDir, spec) {
|
|
|
930
953
|
|
|
931
954
|
return { item: `gcf/work/${spec.id}.md`, hash };
|
|
932
955
|
}
|
|
956
|
+
|
|
957
|
+
/**
|
|
958
|
+
* A committed move holds the main lock from its first read to its commit (Methodology §7): one
|
|
959
|
+
* that read the ledger before taking it would write over a move committed meanwhile.
|
|
960
|
+
* @template T
|
|
961
|
+
* @param {string} projectDir
|
|
962
|
+
* @param {boolean} commit
|
|
963
|
+
* @param {() => Promise<T>} move
|
|
964
|
+
*/
|
|
965
|
+
const locked = (projectDir, commit, move) => (commit ? withMainLock(projectDir, move) : move());
|
|
966
|
+
|
|
967
|
+
/** @type {typeof submitItemUnlocked} */
|
|
968
|
+
export const submitItem = (projectDir, id, opts = {}) =>
|
|
969
|
+
locked(projectDir, opts.commit ?? true, () => submitItemUnlocked(projectDir, id, opts));
|
|
970
|
+
/** @type {typeof closeItemUnlocked} */
|
|
971
|
+
export const closeItem = (projectDir, id, opts = {}) =>
|
|
972
|
+
locked(projectDir, opts.commit ?? true, () => closeItemUnlocked(projectDir, id, opts));
|
|
973
|
+
/** @type {typeof verdictItemUnlocked} */
|
|
974
|
+
export const verdictItem = (projectDir, id, verdict, opts = {}) =>
|
|
975
|
+
locked(projectDir, opts.commit ?? true, () => verdictItemUnlocked(projectDir, id, verdict, opts));
|
|
976
|
+
/** @type {typeof decideItemUnlocked} */
|
|
977
|
+
export const decideItem = (projectDir, id, choice, opts = {}) =>
|
|
978
|
+
locked(projectDir, opts.commit ?? true, () => decideItemUnlocked(projectDir, id, choice, opts));
|
|
979
|
+
/** @type {typeof blockItemUnlocked} */
|
|
980
|
+
export const blockItem = (projectDir, id, opts = {}) =>
|
|
981
|
+
locked(projectDir, opts.commit ?? true, () => blockItemUnlocked(projectDir, id, opts));
|
|
982
|
+
/** @type {typeof unblockItemUnlocked} */
|
|
983
|
+
export const unblockItem = (projectDir, id, opts = {}) =>
|
|
984
|
+
locked(projectDir, opts.commit ?? true, () => unblockItemUnlocked(projectDir, id, opts));
|
|
985
|
+
/** @type {typeof dropItemUnlocked} */
|
|
986
|
+
export const dropItem = (projectDir, id, opts = {}) =>
|
|
987
|
+
locked(projectDir, opts.commit ?? true, () => dropItemUnlocked(projectDir, id, opts));
|
|
988
|
+
/** @type {typeof prioritizeItemsUnlocked} */
|
|
989
|
+
export const prioritizeItems = (projectDir, ids, opts = {}) =>
|
|
990
|
+
locked(projectDir, opts.commit ?? true, () => prioritizeItemsUnlocked(projectDir, ids, opts));
|
|
991
|
+
/** @type {typeof newItemUnlocked} */
|
|
992
|
+
export const newItem = (projectDir, spec) =>
|
|
993
|
+
locked(projectDir, spec.commit ?? false, () => newItemUnlocked(projectDir, spec));
|