@ulysses-ai/create-workspace 0.21.0-beta.0 → 0.23.0-beta.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 (30) hide show
  1. package/lib/init.mjs +9 -0
  2. package/lib/init.test.mjs +75 -0
  3. package/lib/payload.mjs +170 -2
  4. package/lib/payload.test.mjs +158 -3
  5. package/lib/scaffold.mjs +8 -0
  6. package/lib/scaffold.test.mjs +20 -0
  7. package/lib/upgrade.mjs +148 -6
  8. package/lib/upgrade.test.mjs +319 -15
  9. package/package.json +1 -1
  10. package/template/_claude/rules/forge-operations.md +27 -6
  11. package/template/_claude/scripts/chat-record.mjs +51 -4
  12. package/template/_claude/scripts/classify-update.mjs +474 -38
  13. package/template/_claude/scripts/cleanup-work-session.mjs +64 -3
  14. package/template/_claude/scripts/forges/gitlab.mjs +450 -18
  15. package/template/_claude/scripts/forges/interface.mjs +39 -6
  16. package/template/_claude/scripts/maintenance-audit.mjs +0 -0
  17. package/template/_claude/scripts/merge-mode.mjs +96 -12
  18. package/template/_claude/scripts/migrate-sessions.mjs +232 -24
  19. package/template/_claude/scripts/task-pr.mjs +52 -13
  20. package/template/_claude/scripts/task-worktree.mjs +79 -10
  21. package/template/_claude/scripts/template-baseline.mjs +239 -0
  22. package/template/_claude/scripts/trackers/gitlab-issues.mjs +276 -0
  23. package/template/_claude/scripts/trackers/interface.mjs +3 -0
  24. package/template/_claude/skills/complete-work/SKILL.md +7 -4
  25. package/template/_claude/skills/migrate-sessions/SKILL.md +20 -4
  26. package/template/_claude/skills/release/SKILL.md +24 -8
  27. package/template/_claude/skills/setup-tracker/SKILL.md +46 -12
  28. package/template/_claude/skills/start-work/SKILL.md +15 -2
  29. package/template/_claude/skills/workspace-init/SKILL.md +6 -0
  30. package/template/_claude/skills/workspace-update/SKILL.md +63 -27
@@ -21,6 +21,7 @@
21
21
  // node chat-record.mjs --root <dir> --read <chat-name>
22
22
  // node chat-record.mjs --root <dir> --reconcile --session-id <id> --name <n>
23
23
  // node chat-record.mjs --root <dir> --whoami
24
+ // node chat-record.mjs --root <dir> --owner <work-item>
24
25
  // node chat-record.mjs --root <dir> --add-task --chat <n> --branch <b> [--repo <r>] [--work-item <id>]
25
26
  // node chat-record.mjs --root <dir> --remove-task --chat <n> (--work-item <id> | --branch <b>) [--repo <r>]
26
27
  //
@@ -31,11 +32,14 @@
31
32
  // should read `action`. --whoami prints this chat's record name by matching
32
33
  // $CLAUDE_CODE_SESSION_ID against the records' sessionId, nothing and exit
33
34
  // 1 when there is no match: the `Chat record:` hook line can be missing
34
- // after context compaction, and this is the recovery path.
35
+ // after context compaction, and this is the recovery path. --owner prints
36
+ // the chat a work item's open task belongs to as `{ chat, branch, repos }`,
37
+ // and nothing with exit 1 when no record lists it — /start-work uses it to
38
+ // offer adopting a task another chat started (gh:188).
35
39
 
36
40
  import {
37
41
  readFileSync, writeFileSync, existsSync, mkdirSync,
38
- readdirSync, renameSync, rmSync, realpathSync,
42
+ readdirSync, renameSync, rmSync, realpathSync, statSync,
39
43
  } from 'node:fs';
40
44
  import { join, resolve } from 'node:path';
41
45
  import { homedir } from 'node:os';
@@ -241,6 +245,36 @@ function whoami(root, { env = process.env } = {}) {
241
245
  return mine ? mine.chat : null;
242
246
  }
243
247
 
248
+ // The chat a work item's open task belongs to (gh:188). /start-work on an
249
+ // issue that already has a task needs the owner before it can ask the
250
+ // operator whether to adopt the task or leave it — and the record name
251
+ // returned here is the session-registry name SendMessage reaches a chat
252
+ // by, so it doubles as the address for "ask the owner". Records are scanned
253
+ // most-recently-modified first, so a stray duplicate answers with the chat
254
+ // that touched its record last. Null when no record lists the work item.
255
+ function findOwner(root, workItem) {
256
+ if (!workItem) return null;
257
+ const dir = chatsDir(root);
258
+ let names;
259
+ try {
260
+ names = readdirSync(dir).filter((n) => n.endsWith('.json'));
261
+ } catch {
262
+ return null; // no records at all
263
+ }
264
+ names.sort((a, b) => statSync(join(dir, b)).mtimeMs - statSync(join(dir, a)).mtimeMs);
265
+ for (const name of names) {
266
+ const rec = readRecord(root, name.slice(0, -5));
267
+ const hits = rec && Array.isArray(rec.tasks)
268
+ ? rec.tasks.filter((t) => (t.workItem ?? null) === workItem)
269
+ : [];
270
+ if (hits.length === 0) continue;
271
+ // One issue is one task: a shared branch across repos. All matching
272
+ // entries are that task's repos.
273
+ return { chat: rec.chat, branch: hits[0].branch, repos: hits.map((t) => t.repo) };
274
+ }
275
+ return null;
276
+ }
277
+
244
278
  // Scope is what a chat declares it owns. The Aug 26 coordination burst had
245
279
  // sessions declaring this by hand in chat messages; recording it makes it
246
280
  // answerable without asking.
@@ -262,6 +296,7 @@ function parseArgs(argv) {
262
296
  if (a === '--read') { args.mode = 'read'; args.chat = rest[++i]; continue; }
263
297
  if (a === '--reconcile') { args.mode = 'reconcile'; continue; }
264
298
  if (a === '--whoami') { args.mode = 'whoami'; continue; }
299
+ if (a === '--owner') { args.mode = 'owner'; args.workItem = rest[++i]; continue; }
265
300
  if (a === '--add-task') { args.mode = 'add-task'; continue; }
266
301
  if (a === '--remove-task') { args.mode = 'remove-task'; continue; }
267
302
  if (a === '--chat') { args.chat = rest[++i]; continue; }
@@ -272,7 +307,10 @@ function parseArgs(argv) {
272
307
  if (a === '--name') { args.name = rest[++i]; continue; }
273
308
  throw new Error(`unknown argument: ${a}`);
274
309
  }
275
- if (!args.mode) throw new Error('one of --list, --read <chat>, --reconcile, --whoami is required');
310
+ if (!args.mode) throw new Error('one of --list, --read <chat>, --reconcile, --whoami, --owner <work-item> is required');
311
+ if (args.mode === 'owner' && !args.workItem) {
312
+ throw new Error('--owner requires a work item');
313
+ }
276
314
  if (args.mode === 'reconcile' && (!args.sessionId || !args.name)) {
277
315
  throw new Error('--reconcile requires --session-id and --name');
278
316
  }
@@ -296,6 +334,15 @@ function main() {
296
334
  process.stdout.write(`${name}\n`);
297
335
  return;
298
336
  }
337
+ if (args.mode === 'owner') {
338
+ // Same exit-1 contract as --whoami: no owning chat prints nothing, and
339
+ // the caller (a /start-work adoption check) treats that as "no task
340
+ // exists for this issue yet".
341
+ const owner = findOwner(args.root, args.workItem);
342
+ if (owner === null) process.exit(1);
343
+ process.stdout.write(`${JSON.stringify(owner, null, 2)}\n`);
344
+ return;
345
+ }
299
346
  let out;
300
347
  if (args.mode === 'list') out = listRecords(args.root);
301
348
  else if (args.mode === 'read') out = readRecord(args.root, args.chat);
@@ -319,5 +366,5 @@ if (isMainModule(import.meta.url)) {
319
366
  export {
320
367
  recordPath, drawerPath, emptyRecord, readRecord, writeRecord,
321
368
  listRecords, reconcile, parseArgs, readSessionRegistry, resolveChatName,
322
- addTask, removeTask, setScope, whoami, CHATS_DIR,
369
+ addTask, removeTask, setScope, whoami, findOwner, CHATS_DIR,
323
370
  };