@quolu/lattice 0.48.0 → 0.49.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/package.json +1 -1
- package/src/cli-help.mjs +4 -1
- package/src/todo-cli.mjs +25 -0
- package/src/todo-dashboard-registry.mjs +47 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quolu/lattice",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.49.0",
|
|
4
4
|
"description": "Schedulability compiler for multi-agent development: observe real code boundaries, refactor the conflicting seam, recompile the plan for parallel execution",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Quo / クオ at kitepon.dev",
|
package/src/cli-help.mjs
CHANGED
|
@@ -75,6 +75,7 @@ Read commands:
|
|
|
75
75
|
|
|
76
76
|
Write commands:
|
|
77
77
|
dashboard adopt --json # 衝突したproject_idの配信元rootを現在repoへ明示的に移す
|
|
78
|
+
dashboard remove <project_id> --json # 登録簿から1件外す(対象repoの外からも叩ける)
|
|
78
79
|
note --plan <key> [--task <id>] (--message <text>|--input <file>)
|
|
79
80
|
# ToDoへ作業継続に必要な方針・調査結果・注意をappend-onlyで追記する
|
|
80
81
|
migrate --input <extraction.json> [--serialization-reviewed]
|
|
@@ -200,8 +201,10 @@ const SUBCOMMAND_USAGE = Object.freeze({
|
|
|
200
201
|
'todo snapshot': 'todo snapshot --rebuild --plan <key>',
|
|
201
202
|
'todo gantt': 'todo gantt serve --port <port> [--scope live|all] # 動的表示のみ。静的HTML生成は廃止',
|
|
202
203
|
'todo gantt serve': 'todo gantt serve --port <0..65535> [--scope live|all] # loopback動的viewer',
|
|
203
|
-
'todo dashboard': 'todo dashboard adopt --json
|
|
204
|
+
'todo dashboard': 'todo dashboard adopt --json | remove <project_id> --json'
|
|
205
|
+
+ ' # 配信元rootの衝突を明示的に解消/登録簿から1件外す',
|
|
204
206
|
'todo dashboard adopt': 'todo dashboard adopt --json # 現在repoをproject_idの配信元として明示採用',
|
|
207
|
+
'todo dashboard remove': 'todo dashboard remove <project_id> --json # 登録簿から1件外す(対象repo不在でも可)',
|
|
205
208
|
'todo phase': 'todo phase <status|review|accept|reject|reopen|close-unaudited> --plan <key> [options]'
|
|
206
209
|
+ ' | baseline --reason <text> [--except <plan_key>]...',
|
|
207
210
|
'todo phase status': 'todo phase status --plan <key>',
|
package/src/todo-cli.mjs
CHANGED
|
@@ -25,6 +25,7 @@ import { projectTodoChainV1 } from './todo-chain.mjs';
|
|
|
25
25
|
import {
|
|
26
26
|
adoptTodoDashboardActivity,
|
|
27
27
|
ensureTodoDashboardActivity,
|
|
28
|
+
removeTodoDashboardProject,
|
|
28
29
|
} from './todo-dashboard-registry.mjs';
|
|
29
30
|
import { resolveProjectIdentity } from './project-identity.mjs';
|
|
30
31
|
import { layoutTodoGantt } from './todo-gantt-layout.mjs';
|
|
@@ -2568,6 +2569,30 @@ export async function runTodoCli({ argv, cwd, stdout, stderr, env = process.env
|
|
|
2568
2569
|
}
|
|
2569
2570
|
}
|
|
2570
2571
|
|
|
2572
|
+
// 登録を外したいprojectのrepoは、もう存在しないことが普通である(それが外す理由になる)。
|
|
2573
|
+
// 通常dispatchより前に処理し、repoRoot解決・store読取・dashboard daemon起動を経由させない。
|
|
2574
|
+
if (argv.length === 4 && argv[0] === 'dashboard' && argv[1] === 'remove'
|
|
2575
|
+
&& isTodoIdentifier(argv[2]) && argv[3] === '--json') {
|
|
2576
|
+
try {
|
|
2577
|
+
await removeTodoDashboardProject({ projectId: argv[2], env });
|
|
2578
|
+
const result = {
|
|
2579
|
+
schema: 'lattice.todo_dashboard_remove_result.v1',
|
|
2580
|
+
project_id: argv[2],
|
|
2581
|
+
removed: true,
|
|
2582
|
+
result_digest: '',
|
|
2583
|
+
};
|
|
2584
|
+
result.result_digest = todoSelfDigest(result, 'result_digest');
|
|
2585
|
+
stdout.write(`${JSON.stringify(result)}\n`);
|
|
2586
|
+
return 0;
|
|
2587
|
+
} catch (error) {
|
|
2588
|
+
if (typeof error?.code === 'string' && error.detail !== null
|
|
2589
|
+
&& typeof error.detail === 'object') return typedFailure(stderr, error);
|
|
2590
|
+
return typedFailure(stderr, {
|
|
2591
|
+
code: 'INTERNAL_FAILURE', message: error?.constructor?.name ?? 'Error',
|
|
2592
|
+
});
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
|
|
2571
2596
|
let action = null;
|
|
2572
2597
|
if ((argv.length === 1 && argv[0] === 'status')
|
|
2573
2598
|
|| (argv.length === 2 && argv[0] === 'status' && argv[1] === '--json')) {
|
|
@@ -2,7 +2,7 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import { randomBytes } from 'node:crypto';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import {
|
|
5
|
-
mkdir, open, readdir, readFile, realpath, rename, rm, writeFile,
|
|
5
|
+
mkdir, open, readdir, readFile, realpath, rename, rm, stat, writeFile,
|
|
6
6
|
} from 'node:fs/promises';
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
|
|
@@ -157,6 +157,17 @@ export async function readVisibleTodoDashboardProjects({
|
|
|
157
157
|
|| left.project_id.localeCompare(right.project_id, 'en'));
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
/**
|
|
161
|
+
* 見るのはrepo_root directoryの存在だけ。`.lattice`の有無で判定すると、storeを持たない
|
|
162
|
+
* 生きたrepoまで登録簿から消える。ENOENT/ENOTDIR以外のerrorは「消えた証拠」ではないので
|
|
163
|
+
* 生存扱いのまま残す——判定できない相手を消す方が損害が大きい。
|
|
164
|
+
*/
|
|
165
|
+
async function repoRootPresent(ref) {
|
|
166
|
+
try { return (await stat(ref)).isDirectory(); } catch (error) {
|
|
167
|
+
return !['ENOENT', 'ENOTDIR'].includes(error?.code);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
160
171
|
export async function registerTodoDashboardActivity({
|
|
161
172
|
repoRoot, projectId, displayName = projectId, sessionId, env = process.env,
|
|
162
173
|
now = new Date(), adopt = false,
|
|
@@ -169,6 +180,7 @@ export async function registerTodoDashboardActivity({
|
|
|
169
180
|
const canonicalRoot = await realpath(repoRoot);
|
|
170
181
|
const refs = paths(env);
|
|
171
182
|
await mkdir(refs.root, { recursive: true, mode: 0o700 });
|
|
183
|
+
const pruned = [];
|
|
172
184
|
await withLock(refs.lock, async () => {
|
|
173
185
|
const current = validateRegistry(await readJson(refs.registry, { schema: REGISTRY_SCHEMA, projects: [] }));
|
|
174
186
|
const existing = current.projects.find((entry) => entry.project_id === projectId);
|
|
@@ -183,13 +195,45 @@ export async function registerTodoDashboardActivity({
|
|
|
183
195
|
};
|
|
184
196
|
throw error;
|
|
185
197
|
}
|
|
186
|
-
|
|
198
|
+
// 死んだ登録を落とす機会はここしかない。登録は全sessionが必ず通る一点であり、
|
|
199
|
+
// 掃除を「人がコマンドを叩いた時」に置くと、誰も叩かないので永久に積み上がる。
|
|
200
|
+
const projects = [];
|
|
201
|
+
for (const entry of current.projects) {
|
|
202
|
+
if (entry.project_id === projectId) continue;
|
|
203
|
+
if (await repoRootPresent(entry.repo_root)) projects.push(entry);
|
|
204
|
+
else pruned.push(entry.project_id);
|
|
205
|
+
}
|
|
187
206
|
projects.push({ project_id: projectId, display_name: displayName, repo_root: canonicalRoot,
|
|
188
207
|
session_id: sessionId, last_seen_at: now.toISOString() });
|
|
189
208
|
projects.sort((left, right) => left.project_id.localeCompare(right.project_id, 'en'));
|
|
209
|
+
pruned.sort((left, right) => left.localeCompare(right, 'en'));
|
|
210
|
+
await atomicJson(refs.registry, { schema: REGISTRY_SCHEMA, projects });
|
|
211
|
+
});
|
|
212
|
+
return { projectId, displayName, repoRoot: canonicalRoot, sessionId, adopted: adopt, pruned };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* 登録簿から1件を明示的に外す唯一の入口。対象repoが既に消えていても叩けるよう、
|
|
217
|
+
* storeもdaemonも経由しない。該当が無いのは暗黙の成功にしない——「消したつもり」で
|
|
218
|
+
* 別のproject_idが残り続ける状態を、成功の応答で隠すことになる。
|
|
219
|
+
*/
|
|
220
|
+
export async function removeTodoDashboardProject({ projectId, env = process.env }) {
|
|
221
|
+
if (!identifier(projectId)) throw new TypeError('dashboard project id is invalid');
|
|
222
|
+
const refs = paths(env);
|
|
223
|
+
await mkdir(refs.root, { recursive: true, mode: 0o700 });
|
|
224
|
+
await withLock(refs.lock, async () => {
|
|
225
|
+
const current = validateRegistry(await readJson(refs.registry, { schema: REGISTRY_SCHEMA, projects: [] }));
|
|
226
|
+
const projects = current.projects.filter((entry) => entry.project_id !== projectId);
|
|
227
|
+
if (projects.length === current.projects.length) {
|
|
228
|
+
const error = new Error('dashboard project is not registered');
|
|
229
|
+
error.code = 'PROJECT_NOT_REGISTERED';
|
|
230
|
+
// 公開・CLI errorへlocal absolute pathを運ばない。project_idだけで対象を特定できる。
|
|
231
|
+
error.detail = { project_id: projectId };
|
|
232
|
+
throw error;
|
|
233
|
+
}
|
|
190
234
|
await atomicJson(refs.registry, { schema: REGISTRY_SCHEMA, projects });
|
|
191
235
|
});
|
|
192
|
-
return { projectId,
|
|
236
|
+
return { projectId, removed: true };
|
|
193
237
|
}
|
|
194
238
|
|
|
195
239
|
/** 配信元rootを移す唯一の明示入口。通常activity登録はこのflagを立てない。 */
|