@open-agent-toolkit/cli 0.0.27 → 0.0.29

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 (32) hide show
  1. package/assets/docs/cli-utilities/config-and-local-state.md +2 -1
  2. package/assets/docs/cli-utilities/configuration.md +111 -0
  3. package/assets/docs/cli-utilities/tool-packs.md +1 -1
  4. package/assets/docs/reference/cli-reference.md +25 -0
  5. package/assets/docs/reference/file-locations.md +2 -1
  6. package/assets/docs/reference/oat-directory-structure.md +1 -0
  7. package/assets/docs/workflows/projects/hill-checkpoints.md +17 -0
  8. package/assets/docs/workflows/projects/lifecycle.md +6 -0
  9. package/assets/docs/workflows/projects/reviews.md +24 -0
  10. package/assets/docs/workflows/skills/index.md +2 -0
  11. package/assets/public-package-versions.json +4 -4
  12. package/assets/skills/oat-project-complete/SKILL.md +20 -1
  13. package/assets/skills/oat-project-implement/SKILL.md +85 -4
  14. package/assets/skills/oat-project-next/SKILL.md +2 -2
  15. package/assets/skills/oat-project-review-provide/SKILL.md +12 -2
  16. package/assets/skills/oat-project-review-receive/SKILL.md +23 -1
  17. package/assets/skills/oat-project-review-receive-remote/SKILL.md +17 -1
  18. package/assets/skills/oat-wrap-up/SKILL.md +417 -0
  19. package/assets/skills/oat-wrap-up/references/automation-recipes.md +100 -0
  20. package/assets/skills/oat-wrap-up/references/report-template.md +90 -0
  21. package/dist/commands/config/index.d.ts +5 -1
  22. package/dist/commands/config/index.d.ts.map +1 -1
  23. package/dist/commands/config/index.js +283 -140
  24. package/dist/commands/init/tools/shared/skill-manifest.d.ts +1 -1
  25. package/dist/commands/init/tools/shared/skill-manifest.d.ts.map +1 -1
  26. package/dist/commands/init/tools/shared/skill-manifest.js +1 -0
  27. package/dist/config/oat-config.d.ts +15 -0
  28. package/dist/config/oat-config.d.ts.map +1 -1
  29. package/dist/config/oat-config.js +54 -0
  30. package/dist/config/resolve.d.ts.map +1 -1
  31. package/dist/config/resolve.js +21 -0
  32. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/config/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGhF,OAAO,EACL,KAAK,SAAS,EACd,KAAK,cAAc,EAMpB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA6CpC,UAAU,yBAAyB;IACjC,mBAAmB,EAAE,CACnB,OAAO,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC,CAAC,CAAC,KAC/C,cAAc,CAAC;IACpB,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;IACxD,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE,kBAAkB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IAClE,mBAAmB,EAAE,CACnB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,cAAc,KACnB,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,mBAAmB,EAAE,CACnB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,CAAC,UAAU,KACnB,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;CAC/B;AAk3BD,wBAAgB,mBAAmB,CACjC,SAAS,GAAE,OAAO,CAAC,yBAAyB,CAAM,GACjD,OAAO,CAwET"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/config/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGhF,OAAO,EACL,KAAK,SAAS,EACd,KAAK,cAAc,EAGnB,KAAK,UAAU,EAOhB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAEL,KAAK,cAAc,EAEpB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAoDpC,UAAU,yBAAyB;IACjC,mBAAmB,EAAE,CACnB,OAAO,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC,CAAC,CAAC,KAC/C,cAAc,CAAC;IACpB,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;IACxD,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE,kBAAkB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IAClE,mBAAmB,EAAE,CACnB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,cAAc,KACnB,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,cAAc,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IAC/D,eAAe,EAAE,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9E,mBAAmB,EAAE,CACnB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,CAAC,UAAU,KACnB,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB,sBAAsB,EAAE,CACtB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,GAAG,EAAE,MAAM,CAAC,UAAU,KACnB,OAAO,CAAC,cAAc,CAAC,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;CAC/B;AAyiCD,wBAAgB,mBAAmB,CACjC,SAAS,GAAE,OAAO,CAAC,yBAAyB,CAAM,GACjD,OAAO,CA0GT"}
@@ -1,7 +1,9 @@
1
+ import { join } from 'node:path';
1
2
  import { buildCommandContext } from '../../app/command-context.js';
2
3
  import { resolveProjectsRoot } from '../shared/oat-paths.js';
3
4
  import { readGlobalOptions } from '../shared/shared.utils.js';
4
- import { readOatConfig, readOatLocalConfig, writeOatConfig, writeOatLocalConfig, } from '../../config/oat-config.js';
5
+ import { readOatConfig, readOatLocalConfig, readUserConfig, writeOatConfig, writeOatLocalConfig, writeUserConfig, } from '../../config/oat-config.js';
6
+ import { resolveEffectiveConfig, } from '../../config/resolve.js';
5
7
  import { resolveProjectRoot } from '../../fs/paths.js';
6
8
  import { Command } from 'commander';
7
9
  import { createConfigDumpCommand } from './dump.js';
@@ -11,6 +13,7 @@ const KEY_ORDER = [
11
13
  'archive.s3Uri',
12
14
  'archive.s3SyncOnComplete',
13
15
  'archive.summaryExportPath',
16
+ 'archive.wrapUpExportPath',
14
17
  'autoReviewAtCheckpoints',
15
18
  'lastPausedProject',
16
19
  'documentation.root',
@@ -26,6 +29,12 @@ const KEY_ORDER = [
26
29
  'tools.research',
27
30
  'tools.utility',
28
31
  'tools.workflows',
32
+ 'workflow.hillCheckpointDefault',
33
+ 'workflow.archiveOnComplete',
34
+ 'workflow.createPrOnComplete',
35
+ 'workflow.postImplementSequence',
36
+ 'workflow.reviewExecutionModel',
37
+ 'workflow.autoNarrowReReviewScope',
29
38
  'worktrees.root',
30
39
  ];
31
40
  const CONFIG_CATALOG = [
@@ -150,6 +159,17 @@ const CONFIG_CATALOG = [
150
159
  owningCommand: 'oat config set archive.summaryExportPath <value>',
151
160
  description: 'Repository-relative directory where completion copies project summaries for durable tracked reference.',
152
161
  },
162
+ {
163
+ key: 'archive.wrapUpExportPath',
164
+ group: 'Shared Repo (.oat/config.json)',
165
+ file: '.oat/config.json',
166
+ scope: 'shared repo',
167
+ type: 'string',
168
+ defaultValue: 'unset',
169
+ mutability: 'read/write',
170
+ owningCommand: 'oat config set archive.wrapUpExportPath <value>',
171
+ description: 'Repository-relative directory where the oat-wrap-up skill writes date-ranged shipping digests. When unset, the skill falls back to `.oat/repo/reference/wrap-ups`.',
172
+ },
153
173
  {
154
174
  key: 'tools.core',
155
175
  group: 'Shared Repo (.oat/config.json)',
@@ -268,8 +288,74 @@ const CONFIG_CATALOG = [
268
288
  type: 'string | null',
269
289
  defaultValue: 'null',
270
290
  mutability: 'read/write',
271
- owningCommand: 'user config APIs (not surfaced via oat config set)',
272
- description: 'User-level active idea fallback when no repo-local active idea is set.',
291
+ owningCommand: 'oat config set activeIdea <value> --user',
292
+ description: 'User-level active idea fallback used when no repo-local active idea is set. Writable via `oat config set activeIdea <value> --user`.',
293
+ },
294
+ {
295
+ key: 'workflow.hillCheckpointDefault',
296
+ group: 'Workflow Preferences (3-layer: local > shared > user)',
297
+ file: '.oat/config.local.json | .oat/config.json | ~/.oat/config.json',
298
+ scope: 'workflow',
299
+ type: 'every | final',
300
+ defaultValue: 'unset',
301
+ mutability: 'read/write',
302
+ owningCommand: 'oat config set workflow.hillCheckpointDefault <value>',
303
+ description: 'Default HiLL checkpoint behavior in oat-project-implement: "every" pauses after every phase, "final" pauses only after the last phase. When unset, the skill prompts. Resolution: env > local > shared > user > default.',
304
+ },
305
+ {
306
+ key: 'workflow.archiveOnComplete',
307
+ group: 'Workflow Preferences (3-layer: local > shared > user)',
308
+ file: '.oat/config.local.json | .oat/config.json | ~/.oat/config.json',
309
+ scope: 'workflow',
310
+ type: 'boolean',
311
+ defaultValue: 'unset',
312
+ mutability: 'read/write',
313
+ owningCommand: 'oat config set workflow.archiveOnComplete <true|false>',
314
+ description: 'Skip the "Archive after completion?" prompt in oat-project-complete. When unset, the skill prompts. Resolution: env > local > shared > user > default.',
315
+ },
316
+ {
317
+ key: 'workflow.createPrOnComplete',
318
+ group: 'Workflow Preferences (3-layer: local > shared > user)',
319
+ file: '.oat/config.local.json | .oat/config.json | ~/.oat/config.json',
320
+ scope: 'workflow',
321
+ type: 'boolean',
322
+ defaultValue: 'unset',
323
+ mutability: 'read/write',
324
+ owningCommand: 'oat config set workflow.createPrOnComplete <true|false>',
325
+ description: 'Skip the "Open a PR?" prompt in oat-project-complete. When true, completion auto-triggers PR creation. Resolution: env > local > shared > user > default.',
326
+ },
327
+ {
328
+ key: 'workflow.postImplementSequence',
329
+ group: 'Workflow Preferences (3-layer: local > shared > user)',
330
+ file: '.oat/config.local.json | .oat/config.json | ~/.oat/config.json',
331
+ scope: 'workflow',
332
+ type: 'wait | summary | pr | docs-pr',
333
+ defaultValue: 'unset',
334
+ mutability: 'read/write',
335
+ owningCommand: 'oat config set workflow.postImplementSequence <value>',
336
+ description: 'Default post-implementation chaining: "wait" stops without auto-chaining, "summary" generates summary only, "pr" runs pr-final (which auto-generates summary), "docs-pr" runs docs sync then pr-final. When unset, the skill prompts. Resolution: env > local > shared > user > default.',
337
+ },
338
+ {
339
+ key: 'workflow.reviewExecutionModel',
340
+ group: 'Workflow Preferences (3-layer: local > shared > user)',
341
+ file: '.oat/config.local.json | .oat/config.json | ~/.oat/config.json',
342
+ scope: 'workflow',
343
+ type: 'subagent | inline | fresh-session',
344
+ defaultValue: 'unset',
345
+ mutability: 'read/write',
346
+ owningCommand: 'oat config set workflow.reviewExecutionModel <value>',
347
+ description: 'Default execution model for the final review step in oat-project-implement: "subagent" dispatches a review subagent, "inline" runs the review in-context, "fresh-session" prints guidance for running the review in a separate session (with an escape hatch to subagent/inline). When unset, the skill prompts. Resolution: env > local > shared > user > default.',
348
+ },
349
+ {
350
+ key: 'workflow.autoNarrowReReviewScope',
351
+ group: 'Workflow Preferences (3-layer: local > shared > user)',
352
+ file: '.oat/config.local.json | .oat/config.json | ~/.oat/config.json',
353
+ scope: 'workflow',
354
+ type: 'boolean',
355
+ defaultValue: 'unset',
356
+ mutability: 'read/write',
357
+ owningCommand: 'oat config set workflow.autoNarrowReReviewScope <true|false>',
358
+ description: 'Auto-narrow re-review scope to fix-task commits in oat-project-review-provide when re-reviewing completed fix tasks. Has no effect on initial reviews (there is nothing to narrow to). When unset, the skill prompts. Resolution: env > local > shared > user > default.',
273
359
  },
274
360
  {
275
361
  key: 'sync.defaultStrategy',
@@ -312,7 +398,10 @@ const DEFAULT_DEPENDENCIES = {
312
398
  writeOatConfig,
313
399
  readOatLocalConfig,
314
400
  writeOatLocalConfig,
401
+ readUserConfig,
402
+ writeUserConfig,
315
403
  resolveProjectsRoot,
404
+ resolveEffectiveConfig,
316
405
  processEnv: process.env,
317
406
  };
318
407
  function isConfigKey(value) {
@@ -325,150 +414,168 @@ function normalizeSharedRoot(value) {
325
414
  }
326
415
  return trimmed.replace(/\/+$/, '');
327
416
  }
328
- async function resolveProjectsRootWithSource(repoRoot, dependencies) {
329
- const envRoot = dependencies.processEnv.OAT_PROJECTS_ROOT?.trim();
330
- if (envRoot) {
331
- return {
332
- key: 'projects.root',
333
- value: envRoot.replace(/\/+$/, ''),
334
- source: 'env',
335
- };
417
+ const WORKFLOW_ENUM_VALUES = {
418
+ 'workflow.hillCheckpointDefault': ['every', 'final'],
419
+ 'workflow.postImplementSequence': ['wait', 'summary', 'pr', 'docs-pr'],
420
+ 'workflow.reviewExecutionModel': ['subagent', 'inline', 'fresh-session'],
421
+ };
422
+ const WORKFLOW_BOOLEAN_KEYS = new Set([
423
+ 'workflow.archiveOnComplete',
424
+ 'workflow.createPrOnComplete',
425
+ 'workflow.autoNarrowReReviewScope',
426
+ ]);
427
+ function isWorkflowKey(key) {
428
+ return key.startsWith('workflow.');
429
+ }
430
+ function isStateKey(key) {
431
+ return (key === 'activeIdea' ||
432
+ key === 'activeProject' ||
433
+ key === 'lastPausedProject');
434
+ }
435
+ function isStructuralKey(key) {
436
+ return (key === 'projects.root' ||
437
+ key === 'worktrees.root' ||
438
+ key === 'git.defaultBranch' ||
439
+ key.startsWith('documentation.') ||
440
+ key.startsWith('archive.') ||
441
+ key.startsWith('tools.'));
442
+ }
443
+ function validateSurfaceForKey(key, surface) {
444
+ if (surface === 'auto') {
445
+ return;
336
446
  }
337
- const config = await dependencies.readOatConfig(repoRoot);
338
- const configRoot = config.projects?.root?.trim();
339
- if (configRoot) {
340
- return {
341
- key: 'projects.root',
342
- value: configRoot.replace(/\/+$/, ''),
343
- source: 'config.json',
344
- };
447
+ if (isStructuralKey(key)) {
448
+ if (surface !== 'shared') {
449
+ throw new Error(`Cannot set structural key '${key}' at '${surface}' scope. Structural keys (projects.root, worktrees.root, git.*, documentation.*, archive.*, tools.*) can only be set at shared scope (.oat/config.json).`);
450
+ }
451
+ return;
345
452
  }
346
- const value = await dependencies.resolveProjectsRoot(repoRoot, dependencies.processEnv);
347
- return {
348
- key: 'projects.root',
349
- value,
350
- source: 'default',
351
- };
453
+ if (isStateKey(key)) {
454
+ // activeIdea has both a repo-local and a user-level surface in the
455
+ // catalog (the user-level entry is the global fallback). Both surfaces
456
+ // are writable; shared is not supported because an idea pointer is not
457
+ // a team decision.
458
+ if (key === 'activeIdea') {
459
+ if (surface !== 'local' && surface !== 'user') {
460
+ throw new Error(`Cannot set 'activeIdea' at '${surface}' scope. activeIdea can only be set at local scope (.oat/config.local.json) or user scope (~/.oat/config.json).`);
461
+ }
462
+ return;
463
+ }
464
+ // activeProject and lastPausedProject are per-checkout state only.
465
+ if (surface !== 'local') {
466
+ throw new Error(`Cannot set state key '${key}' at '${surface}' scope. State keys (activeProject, lastPausedProject) can only be set at local scope (.oat/config.local.json).`);
467
+ }
468
+ return;
469
+ }
470
+ // autoReviewAtCheckpoints is currently shared-only. Multi-surface support
471
+ // for behavioral keys is out of scope for p01-t04 (workflow.* keys only).
472
+ if (key === 'autoReviewAtCheckpoints' && surface !== 'shared') {
473
+ throw new Error(`Cannot set 'autoReviewAtCheckpoints' at '${surface}' scope. This key is currently shared-only.`);
474
+ }
475
+ // Workflow keys accept any non-auto surface.
352
476
  }
353
- async function getConfigValue(repoRoot, key, dependencies) {
354
- if (key === 'projects.root') {
355
- return resolveProjectsRootWithSource(repoRoot, dependencies);
477
+ function defaultSurfaceForKey(key) {
478
+ if (isWorkflowKey(key) || isStateKey(key)) {
479
+ return 'local';
356
480
  }
357
- if (key.startsWith('documentation.')) {
358
- const config = await dependencies.readOatConfig(repoRoot);
359
- const doc = config.documentation;
360
- let value = null;
361
- if (key === 'documentation.root') {
362
- value = doc?.root ?? null;
363
- }
364
- else if (key === 'documentation.tooling') {
365
- value = doc?.tooling ?? null;
366
- }
367
- else if (key === 'documentation.config') {
368
- value = doc?.config ?? null;
369
- }
370
- else if (key === 'documentation.requireForProjectCompletion') {
371
- value =
372
- doc?.requireForProjectCompletion != null
373
- ? String(doc.requireForProjectCompletion)
374
- : 'false';
481
+ return 'shared';
482
+ }
483
+ function parseWorkflowValue(key, rawValue) {
484
+ if (WORKFLOW_BOOLEAN_KEYS.has(key)) {
485
+ const normalized = rawValue.trim().toLowerCase();
486
+ if (normalized !== 'true' && normalized !== 'false') {
487
+ throw new Error(`Invalid value for ${key}: expected 'true' or 'false', got '${rawValue}'`);
375
488
  }
376
- return {
377
- key,
378
- value,
379
- source: doc ? 'config.json' : 'default',
380
- };
489
+ return normalized === 'true';
381
490
  }
382
- if (key.startsWith('archive.')) {
383
- const config = await dependencies.readOatConfig(repoRoot);
384
- const archive = config.archive;
385
- let value = null;
386
- if (key === 'archive.s3Uri') {
387
- value = archive?.s3Uri ?? null;
388
- }
389
- else if (key === 'archive.s3SyncOnComplete') {
390
- value =
391
- archive?.s3SyncOnComplete != null
392
- ? String(archive.s3SyncOnComplete)
393
- : 'false';
394
- }
395
- else if (key === 'archive.summaryExportPath') {
396
- value = archive?.summaryExportPath ?? null;
491
+ const allowed = WORKFLOW_ENUM_VALUES[key];
492
+ if (allowed) {
493
+ const normalized = rawValue.trim();
494
+ if (!allowed.includes(normalized)) {
495
+ throw new Error(`Invalid value for ${key}: expected one of ${allowed.join(' | ')}, got '${rawValue}'`);
397
496
  }
398
- return {
399
- key,
400
- value,
401
- source: archive ? 'config.json' : 'default',
402
- };
497
+ return normalized;
403
498
  }
404
- if (key.startsWith('tools.')) {
405
- const config = await dependencies.readOatConfig(repoRoot);
406
- const packName = key.slice('tools.'.length);
407
- const tools = config.tools ?? {};
408
- return {
409
- key,
410
- value: String(tools[packName] ?? false),
411
- source: config.tools ? 'config.json' : 'default',
412
- };
499
+ throw new Error(`Unknown workflow key: ${key}`);
500
+ }
501
+ function applyWorkflowValue(workflow, key, value) {
502
+ const subKey = key.slice('workflow.'.length);
503
+ return {
504
+ ...workflow,
505
+ [subKey]: value,
506
+ };
507
+ }
508
+ function formatResolvedValue(value) {
509
+ if (value === null || value === undefined) {
510
+ return null;
413
511
  }
414
- if (key === 'git.defaultBranch') {
415
- const config = await dependencies.readOatConfig(repoRoot);
416
- return {
417
- key,
418
- value: config.git?.defaultBranch ?? null,
419
- source: config.git?.defaultBranch ? 'config.json' : 'default',
420
- };
512
+ if (typeof value === 'boolean') {
513
+ return String(value);
421
514
  }
422
- if (key === 'worktrees.root') {
423
- const envRoot = dependencies.processEnv.OAT_WORKTREES_ROOT?.trim();
424
- if (envRoot) {
425
- return {
426
- key,
427
- value: envRoot.replace(/\/+$/, ''),
428
- source: 'env',
429
- };
430
- }
431
- const config = await dependencies.readOatConfig(repoRoot);
432
- const value = config.worktrees?.root?.trim();
433
- if (value) {
434
- return {
435
- key,
436
- value: value.replace(/\/+$/, ''),
437
- source: 'config.json',
438
- };
439
- }
440
- return {
441
- key,
442
- value: '.worktrees',
443
- source: 'default',
444
- };
515
+ if (typeof value === 'string') {
516
+ return value;
445
517
  }
446
- if (key === 'autoReviewAtCheckpoints') {
447
- const config = await dependencies.readOatConfig(repoRoot);
448
- return {
449
- key,
450
- value: config.autoReviewAtCheckpoints != null
451
- ? String(config.autoReviewAtCheckpoints)
452
- : 'false',
453
- source: config.autoReviewAtCheckpoints != null ? 'config.json' : 'default',
454
- };
518
+ if (Array.isArray(value)) {
519
+ return value.join(',');
520
+ }
521
+ return String(value);
522
+ }
523
+ async function getConfigValue(repoRoot, userConfigDir, key, dependencies) {
524
+ const resolved = await dependencies.resolveEffectiveConfig(repoRoot, userConfigDir, dependencies.processEnv);
525
+ const entry = resolved.resolved[key];
526
+ if (!entry) {
527
+ return { key, value: null, source: 'default' };
455
528
  }
456
- const localConfig = await dependencies.readOatLocalConfig(repoRoot);
457
- const localKey = key;
458
- const hasKey = Object.hasOwn(localConfig, localKey);
459
- const value = localConfig[localKey] ?? null;
460
529
  return {
461
530
  key,
462
- value,
463
- source: hasKey ? 'config.local.json' : 'default',
531
+ value: formatResolvedValue(entry.value),
532
+ source: entry.source,
464
533
  };
465
534
  }
466
- async function setConfigValue(repoRoot, key, rawValue, dependencies) {
535
+ async function setConfigValue(repoRoot, userConfigDir, key, rawValue, surface, dependencies) {
536
+ validateSurfaceForKey(key, surface);
537
+ const effectiveSurface = surface === 'auto' ? defaultSurfaceForKey(key) : surface;
538
+ if (isWorkflowKey(key)) {
539
+ const parsedValue = parseWorkflowValue(key, rawValue);
540
+ const displayValue = typeof parsedValue === 'boolean' ? String(parsedValue) : parsedValue;
541
+ if (effectiveSurface === 'user') {
542
+ const userConfig = await dependencies.readUserConfig(userConfigDir);
543
+ await dependencies.writeUserConfig(userConfigDir, {
544
+ ...userConfig,
545
+ workflow: applyWorkflowValue(userConfig.workflow ?? {}, key, parsedValue),
546
+ });
547
+ return { key, value: displayValue, source: 'user' };
548
+ }
549
+ if (effectiveSurface === 'local') {
550
+ const localConfig = await dependencies.readOatLocalConfig(repoRoot);
551
+ await dependencies.writeOatLocalConfig(repoRoot, {
552
+ ...localConfig,
553
+ workflow: applyWorkflowValue(localConfig.workflow ?? {}, key, parsedValue),
554
+ });
555
+ return { key, value: displayValue, source: 'local' };
556
+ }
557
+ // shared
558
+ const sharedConfig = await dependencies.readOatConfig(repoRoot);
559
+ await dependencies.writeOatConfig(repoRoot, {
560
+ ...sharedConfig,
561
+ workflow: applyWorkflowValue(sharedConfig.workflow ?? {}, key, parsedValue),
562
+ });
563
+ return { key, value: displayValue, source: 'shared' };
564
+ }
467
565
  if (key === 'activeIdea' ||
468
566
  key === 'activeProject' ||
469
567
  key === 'lastPausedProject') {
470
- const localConfig = await dependencies.readOatLocalConfig(repoRoot);
471
568
  const nextValue = rawValue === '' ? null : rawValue;
569
+ // activeIdea --user writes to ~/.oat/config.json
570
+ if (key === 'activeIdea' && effectiveSurface === 'user') {
571
+ const userConfig = await dependencies.readUserConfig(userConfigDir);
572
+ await dependencies.writeUserConfig(userConfigDir, {
573
+ ...userConfig,
574
+ activeIdea: nextValue,
575
+ });
576
+ return { key, value: nextValue, source: 'user' };
577
+ }
578
+ const localConfig = await dependencies.readOatLocalConfig(repoRoot);
472
579
  await dependencies.writeOatLocalConfig(repoRoot, {
473
580
  ...localConfig,
474
581
  [key]: nextValue,
@@ -476,7 +583,7 @@ async function setConfigValue(repoRoot, key, rawValue, dependencies) {
476
583
  return {
477
584
  key,
478
585
  value: nextValue,
479
- source: 'config.local.json',
586
+ source: 'local',
480
587
  };
481
588
  }
482
589
  const config = await dependencies.readOatConfig(repoRoot);
@@ -505,7 +612,7 @@ async function setConfigValue(repoRoot, key, rawValue, dependencies) {
505
612
  return {
506
613
  key,
507
614
  value: resultValue,
508
- source: 'config.json',
615
+ source: 'shared',
509
616
  };
510
617
  }
511
618
  if (key.startsWith('archive.')) {
@@ -519,6 +626,9 @@ async function setConfigValue(repoRoot, key, rawValue, dependencies) {
519
626
  else if (key === 'archive.summaryExportPath') {
520
627
  archive.summaryExportPath = normalizeSharedRoot(rawValue);
521
628
  }
629
+ else if (key === 'archive.wrapUpExportPath') {
630
+ archive.wrapUpExportPath = normalizeSharedRoot(rawValue);
631
+ }
522
632
  await dependencies.writeOatConfig(repoRoot, {
523
633
  ...config,
524
634
  archive,
@@ -529,7 +639,7 @@ async function setConfigValue(repoRoot, key, rawValue, dependencies) {
529
639
  return {
530
640
  key,
531
641
  value: resultValue,
532
- source: 'config.json',
642
+ source: 'shared',
533
643
  };
534
644
  }
535
645
  if (key.startsWith('tools.')) {
@@ -543,7 +653,7 @@ async function setConfigValue(repoRoot, key, rawValue, dependencies) {
543
653
  return {
544
654
  key,
545
655
  value: String(tools[packName] ?? false),
546
- source: 'config.json',
656
+ source: 'shared',
547
657
  };
548
658
  }
549
659
  if (key === 'git.defaultBranch') {
@@ -561,7 +671,7 @@ async function setConfigValue(repoRoot, key, rawValue, dependencies) {
561
671
  return {
562
672
  key,
563
673
  value: nextValue,
564
- source: 'config.json',
674
+ source: 'shared',
565
675
  };
566
676
  }
567
677
  if (key === 'autoReviewAtCheckpoints') {
@@ -573,7 +683,7 @@ async function setConfigValue(repoRoot, key, rawValue, dependencies) {
573
683
  return {
574
684
  key,
575
685
  value: String(nextValue),
576
- source: 'config.json',
686
+ source: 'shared',
577
687
  };
578
688
  }
579
689
  const normalizedValue = normalizeSharedRoot(rawValue);
@@ -592,7 +702,7 @@ async function setConfigValue(repoRoot, key, rawValue, dependencies) {
592
702
  return {
593
703
  key,
594
704
  value: normalizedValue,
595
- source: 'config.json',
705
+ source: 'shared',
596
706
  };
597
707
  }
598
708
  function formatList(values) {
@@ -656,7 +766,8 @@ async function runGet(keyArg, context, dependencies) {
656
766
  throw new Error(`Unknown config key: ${keyArg}`);
657
767
  }
658
768
  const repoRoot = await dependencies.resolveProjectRoot(context.cwd);
659
- const value = await getConfigValue(repoRoot, keyArg, dependencies);
769
+ const userConfigDir = join(context.home, '.oat');
770
+ const value = await getConfigValue(repoRoot, userConfigDir, keyArg, dependencies);
660
771
  if (context.json) {
661
772
  context.logger.json({
662
773
  status: 'ok',
@@ -679,13 +790,14 @@ async function runGet(keyArg, context, dependencies) {
679
790
  process.exitCode = 1;
680
791
  }
681
792
  }
682
- async function runSet(keyArg, rawValue, context, dependencies) {
793
+ async function runSet(keyArg, rawValue, surface, context, dependencies) {
683
794
  try {
684
795
  if (!isConfigKey(keyArg)) {
685
796
  throw new Error(`Unknown config key: ${keyArg}`);
686
797
  }
687
798
  const repoRoot = await dependencies.resolveProjectRoot(context.cwd);
688
- const result = await setConfigValue(repoRoot, keyArg, rawValue, dependencies);
799
+ const userConfigDir = join(context.home, '.oat');
800
+ const result = await setConfigValue(repoRoot, userConfigDir, keyArg, rawValue, surface, dependencies);
689
801
  if (context.json) {
690
802
  context.logger.json({
691
803
  status: 'ok',
@@ -711,9 +823,10 @@ async function runSet(keyArg, rawValue, context, dependencies) {
711
823
  async function runList(context, dependencies) {
712
824
  try {
713
825
  const repoRoot = await dependencies.resolveProjectRoot(context.cwd);
826
+ const userConfigDir = join(context.home, '.oat');
714
827
  const values = [];
715
828
  for (const key of KEY_ORDER) {
716
- values.push(await getConfigValue(repoRoot, key, dependencies));
829
+ values.push(await getConfigValue(repoRoot, userConfigDir, key, dependencies));
717
830
  }
718
831
  if (context.json) {
719
832
  context.logger.json({
@@ -789,9 +902,39 @@ export function createConfigCommand(overrides = {}) {
789
902
  .description('Set an OAT config value')
790
903
  .argument('<key>', 'Config key')
791
904
  .argument('<value>', 'Config value')
792
- .action(async (key, value, _options, command) => {
905
+ .option('--shared', 'Write to the shared repo config (.oat/config.json)')
906
+ .option('--local', 'Write to the repo-local config (.oat/config.local.json)')
907
+ .option('--user', 'Write to the user-level config (~/.oat/config.json)')
908
+ .action(async (key, value, options, command) => {
793
909
  const context = dependencies.buildCommandContext(readGlobalOptions(command));
794
- await runSet(key, value, context, dependencies);
910
+ try {
911
+ const flagsPresent = [
912
+ options.shared,
913
+ options.local,
914
+ options.user,
915
+ ].filter(Boolean).length;
916
+ if (flagsPresent > 1) {
917
+ throw new Error('--shared, --local, and --user flags are mutually exclusive; pass at most one.');
918
+ }
919
+ let surface = 'auto';
920
+ if (options.shared)
921
+ surface = 'shared';
922
+ else if (options.local)
923
+ surface = 'local';
924
+ else if (options.user)
925
+ surface = 'user';
926
+ await runSet(key, value, surface, context, dependencies);
927
+ }
928
+ catch (error) {
929
+ const message = error instanceof Error ? error.message : String(error);
930
+ if (context.json) {
931
+ context.logger.json({ status: 'error', message });
932
+ }
933
+ else {
934
+ context.logger.error(message);
935
+ }
936
+ process.exitCode = 1;
937
+ }
795
938
  }))
796
939
  .addCommand(new Command('list')
797
940
  .description('List resolved OAT config values with sources')
@@ -5,7 +5,7 @@
5
5
  * `bundle-assets.sh` maintains its own bash array — `bundle-consistency.test.ts`
6
6
  * validates that it stays in sync with these lists.
7
7
  */
8
- export declare const WORKFLOW_SKILLS: readonly ["oat-project-capture", "oat-project-clear-active", "oat-project-complete", "oat-project-design", "oat-project-discover", "oat-project-document", "oat-project-implement", "oat-project-import-plan", "oat-project-new", "oat-project-next", "oat-project-open", "oat-project-plan", "oat-project-plan-writing", "oat-project-pr-final", "oat-project-pr-progress", "oat-project-progress", "oat-project-promote-spec-driven", "oat-project-quick-start", "oat-project-reconcile", "oat-project-revise", "oat-project-review-provide", "oat-project-review-receive", "oat-project-review-receive-remote", "oat-project-spec", "oat-project-subagent-implement", "oat-project-summary", "oat-repo-knowledge-index", "oat-worktree-bootstrap", "oat-worktree-bootstrap-auto"];
8
+ export declare const WORKFLOW_SKILLS: readonly ["oat-project-capture", "oat-project-clear-active", "oat-project-complete", "oat-project-design", "oat-project-discover", "oat-project-document", "oat-project-implement", "oat-project-import-plan", "oat-project-new", "oat-project-next", "oat-project-open", "oat-project-plan", "oat-project-plan-writing", "oat-project-pr-final", "oat-project-pr-progress", "oat-project-progress", "oat-project-promote-spec-driven", "oat-project-quick-start", "oat-project-reconcile", "oat-project-revise", "oat-project-review-provide", "oat-project-review-receive", "oat-project-review-receive-remote", "oat-project-spec", "oat-project-subagent-implement", "oat-project-summary", "oat-repo-knowledge-index", "oat-worktree-bootstrap", "oat-worktree-bootstrap-auto", "oat-wrap-up"];
9
9
  export declare const WORKFLOW_AGENTS: readonly ["oat-codebase-mapper.md", "oat-reviewer.md"];
10
10
  export declare const WORKFLOW_TEMPLATES: readonly ["state.md", "discovery.md", "spec.md", "design.md", "plan.md", "implementation.md", "summary.md"];
11
11
  export declare const WORKFLOW_SCRIPTS: readonly ["generate-oat-state.sh", "generate-thin-index.sh", "resolve-tracking.sh"];
@@ -1 +1 @@
1
- {"version":3,"file":"skill-manifest.d.ts","sourceRoot":"","sources":["../../../../../src/commands/init/tools/shared/skill-manifest.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,eAAO,MAAM,eAAe,svBA8BlB,CAAC;AAEX,eAAO,MAAM,eAAe,wDAGlB,CAAC;AAEX,eAAO,MAAM,kBAAkB,6GAQrB,CAAC;AAEX,eAAO,MAAM,gBAAgB,qFAInB,CAAC;AAIX,eAAO,MAAM,WAAW,2FAKd,CAAC;AAIX,eAAO,MAAM,WAAW,qCAAsC,CAAC;AAI/D,eAAO,MAAM,WAAW,mHAKd,CAAC;AAEX,eAAO,MAAM,YAAY,kCAAmC,CAAC;AAI7D,eAAO,MAAM,cAAc,gJAMjB,CAAC;AAIX,eAAO,MAAM,yBAAyB,kGAI5B,CAAC;AAEX,eAAO,MAAM,4BAA4B,4CAG/B,CAAC;AAEX,eAAO,MAAM,0BAA0B,aAAc,CAAC;AAItD,eAAO,MAAM,eAAe,2EAMlB,CAAC;AAEX,eAAO,MAAM,eAAe,qCAAsC,CAAC"}
1
+ {"version":3,"file":"skill-manifest.d.ts","sourceRoot":"","sources":["../../../../../src/commands/init/tools/shared/skill-manifest.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,eAAO,MAAM,eAAe,qwBA+BlB,CAAC;AAEX,eAAO,MAAM,eAAe,wDAGlB,CAAC;AAEX,eAAO,MAAM,kBAAkB,6GAQrB,CAAC;AAEX,eAAO,MAAM,gBAAgB,qFAInB,CAAC;AAIX,eAAO,MAAM,WAAW,2FAKd,CAAC;AAIX,eAAO,MAAM,WAAW,qCAAsC,CAAC;AAI/D,eAAO,MAAM,WAAW,mHAKd,CAAC;AAEX,eAAO,MAAM,YAAY,kCAAmC,CAAC;AAI7D,eAAO,MAAM,cAAc,gJAMjB,CAAC;AAIX,eAAO,MAAM,yBAAyB,kGAI5B,CAAC;AAEX,eAAO,MAAM,4BAA4B,4CAG/B,CAAC;AAEX,eAAO,MAAM,0BAA0B,aAAc,CAAC;AAItD,eAAO,MAAM,eAAe,2EAMlB,CAAC;AAEX,eAAO,MAAM,eAAe,qCAAsC,CAAC"}
@@ -36,6 +36,7 @@ export const WORKFLOW_SKILLS = [
36
36
  'oat-repo-knowledge-index',
37
37
  'oat-worktree-bootstrap',
38
38
  'oat-worktree-bootstrap-auto',
39
+ 'oat-wrap-up',
39
40
  ];
40
41
  export const WORKFLOW_AGENTS = [
41
42
  'oat-codebase-mapper.md',
@@ -12,6 +12,18 @@ export interface OatArchiveConfig {
12
12
  s3Uri?: string;
13
13
  s3SyncOnComplete?: boolean;
14
14
  summaryExportPath?: string;
15
+ wrapUpExportPath?: string;
16
+ }
17
+ export type WorkflowHillCheckpointDefault = 'every' | 'final';
18
+ export type WorkflowPostImplementSequence = 'wait' | 'summary' | 'pr' | 'docs-pr';
19
+ export type WorkflowReviewExecutionModel = 'subagent' | 'inline' | 'fresh-session';
20
+ export interface OatWorkflowConfig {
21
+ hillCheckpointDefault?: WorkflowHillCheckpointDefault;
22
+ archiveOnComplete?: boolean;
23
+ createPrOnComplete?: boolean;
24
+ postImplementSequence?: WorkflowPostImplementSequence;
25
+ reviewExecutionModel?: WorkflowReviewExecutionModel;
26
+ autoNarrowReReviewScope?: boolean;
15
27
  }
16
28
  export type OatToolsConfig = Partial<Record<'core' | 'ideas' | 'docs' | 'workflows' | 'utility' | 'project-management' | 'research', boolean>>;
17
29
  export interface OatConfig {
@@ -28,16 +40,19 @@ export interface OatConfig {
28
40
  documentation?: OatDocumentationConfig;
29
41
  localPaths?: string[];
30
42
  autoReviewAtCheckpoints?: boolean;
43
+ workflow?: OatWorkflowConfig;
31
44
  }
32
45
  export interface OatLocalConfig {
33
46
  version: number;
34
47
  activeProject?: string | null;
35
48
  lastPausedProject?: string | null;
36
49
  activeIdea?: string | null;
50
+ workflow?: OatWorkflowConfig;
37
51
  }
38
52
  export interface UserConfig {
39
53
  version: number;
40
54
  activeIdea?: string | null;
55
+ workflow?: OatWorkflowConfig;
41
56
  }
42
57
  export interface ActiveProjectResolution {
43
58
  name: string | null;