@aopslabs/aops 0.2.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 (171) hide show
  1. package/LICENSE +19 -0
  2. package/NOTICE +7 -0
  3. package/README.md +203 -0
  4. package/assets/agent-assets/core/SKILL.md +49 -0
  5. package/assets/agent-assets/core/references/agentspace/SKILL.md +78 -0
  6. package/assets/agent-assets/core/references/aops-cli-core/SKILL.md +108 -0
  7. package/assets/agent-assets/core/references/chatv3/SKILL.md +48 -0
  8. package/assets/agent-assets/core/references/collaborative-work/SKILL.md +85 -0
  9. package/assets/agent-assets/core/references/discuss/SKILL.md +66 -0
  10. package/assets/agent-assets/core/references/docman/SKILL.md +69 -0
  11. package/assets/agent-assets/core/references/hosted-chat/SKILL.md +64 -0
  12. package/assets/agent-assets/core/references/mission/SKILL.md +54 -0
  13. package/assets/agent-assets/core/references/projectman/SKILL.md +86 -0
  14. package/assets/agent-assets/core/references/sys/SKILL.md +36 -0
  15. package/assets/agent-assets/core/references/view/SKILL.md +58 -0
  16. package/assets/agent-assets/core/references/working-disciplines/SKILL.md +69 -0
  17. package/assets/agent-assets/core/user-guides/agent-assets.md +81 -0
  18. package/assets/agent-assets/core/user-guides/agentspace.md +1098 -0
  19. package/assets/agent-assets/core/user-guides/aops-cli.md +1107 -0
  20. package/assets/agent-assets/core/user-guides/aops-system.md +63 -0
  21. package/assets/agent-assets/core/user-guides/chatv3.md +113 -0
  22. package/assets/agent-assets/core/user-guides/docman.md +162 -0
  23. package/assets/agent-assets/core/user-guides/projectman.md +212 -0
  24. package/assets/agent-assets/core/user-guides/sys.md +94 -0
  25. package/assets/agent-assets/core/user-guides/working-disciplines.md +588 -0
  26. package/assets/agent-assets/gateway/aops/SKILL.md +14 -0
  27. package/assets/r6-remote-session-baseline.json +19 -0
  28. package/assets/skills/aops-install/SKILL.md +231 -0
  29. package/assets/skills/aops-install/agents/openai.yaml +4 -0
  30. package/dist/commands/activity.js +352 -0
  31. package/dist/commands/agent-profile.js +653 -0
  32. package/dist/commands/agent.js +710 -0
  33. package/dist/commands/agents-md.js +125 -0
  34. package/dist/commands/api.js +84 -0
  35. package/dist/commands/archive.js +873 -0
  36. package/dist/commands/artifact.js +535 -0
  37. package/dist/commands/assets.js +1414 -0
  38. package/dist/commands/auth/device-login.js +1 -0
  39. package/dist/commands/auth/legacy-export.js +177 -0
  40. package/dist/commands/auth/login.js +426 -0
  41. package/dist/commands/chat.js +1363 -0
  42. package/dist/commands/chatv3.js +1684 -0
  43. package/dist/commands/checkpoint.js +549 -0
  44. package/dist/commands/commercial-artifact.js +475 -0
  45. package/dist/commands/commercial-license.js +594 -0
  46. package/dist/commands/community-auth.js +62 -0
  47. package/dist/commands/community-cockpit.js +185 -0
  48. package/dist/commands/community-console.js +109 -0
  49. package/dist/commands/community-doctor.js +335 -0
  50. package/dist/commands/community-server.js +2062 -0
  51. package/dist/commands/community-setup.js +284 -0
  52. package/dist/commands/discuss.js +1448 -0
  53. package/dist/commands/doc.js +5219 -0
  54. package/dist/commands/experience.js +922 -0
  55. package/dist/commands/host.js +1127 -0
  56. package/dist/commands/init.js +118 -0
  57. package/dist/commands/memory.js +3129 -0
  58. package/dist/commands/mission.js +562 -0
  59. package/dist/commands/official-catalog.js +134 -0
  60. package/dist/commands/plan.js +84 -0
  61. package/dist/commands/playbook.js +415 -0
  62. package/dist/commands/pm/context.js +6 -0
  63. package/dist/commands/pm/index.js +701 -0
  64. package/dist/commands/pm/projectman.js +6266 -0
  65. package/dist/commands/project.js +1080 -0
  66. package/dist/commands/prompt.js +1024 -0
  67. package/dist/commands/repo-sync.js +1907 -0
  68. package/dist/commands/resource.js +527 -0
  69. package/dist/commands/skill.js +1211 -0
  70. package/dist/commands/start-disciplines.js +510 -0
  71. package/dist/commands/start.js +1343 -0
  72. package/dist/commands/target.js +309 -0
  73. package/dist/commands/version.js +42 -0
  74. package/dist/commands/view.js +1852 -0
  75. package/dist/launchers/cockpit.js +28 -0
  76. package/dist/launchers/options.js +74 -0
  77. package/dist/launchers/server.js +41 -0
  78. package/dist/lib/agent-assets/envelope.js +83 -0
  79. package/dist/lib/agent-assets/gateway.js +15 -0
  80. package/dist/lib/agent-assets/guards.js +23 -0
  81. package/dist/lib/agent-assets/hosted-discovery.js +148 -0
  82. package/dist/lib/agent-assets/hosted-package-input.js +154 -0
  83. package/dist/lib/agent-assets/legacy-pointer-migration.js +677 -0
  84. package/dist/lib/agent-assets/native-fs.js +589 -0
  85. package/dist/lib/agent-assets/package-manifest.js +324 -0
  86. package/dist/lib/agent-assets/portable-path.js +67 -0
  87. package/dist/lib/agent-assets/release-input.js +177 -0
  88. package/dist/lib/agent-assets/roots.js +45 -0
  89. package/dist/lib/agent-assets/runtime-binding-reader.js +545 -0
  90. package/dist/lib/agent-assets/runtime-targets.js +50 -0
  91. package/dist/lib/agent-assets/store-reader.js +1212 -0
  92. package/dist/lib/agent-assets/store-types.js +1 -0
  93. package/dist/lib/agent-assets/store-writer.js +1484 -0
  94. package/dist/lib/agent-assets/types.js +1 -0
  95. package/dist/lib/agent-assets/unicode-case-folding-v15-1.generated.js +1548 -0
  96. package/dist/lib/commercial-final-readiness.js +504 -0
  97. package/dist/lib/commercial-lifecycle-smoke-isolation.js +236 -0
  98. package/dist/lib/commercial-managed-postgres-docker-adapter.js +274 -0
  99. package/dist/lib/commercial-setup-readiness-adapter.js +179 -0
  100. package/dist/lib/community-client-contract.js +164 -0
  101. package/dist/lib/community-cockpit-lifecycle.js +601 -0
  102. package/dist/lib/community-command-abort.js +59 -0
  103. package/dist/lib/community-diagnostic.js +237 -0
  104. package/dist/lib/community-docker-adapter.js +498 -0
  105. package/dist/lib/community-home.js +31 -0
  106. package/dist/lib/community-instance-contract.js +148 -0
  107. package/dist/lib/community-instance-layout.js +97 -0
  108. package/dist/lib/community-lifecycle.js +1631 -0
  109. package/dist/lib/community-migration-snapshot.js +770 -0
  110. package/dist/lib/community-native-application-recovery.js +425 -0
  111. package/dist/lib/community-native-child.js +240 -0
  112. package/dist/lib/community-native-database-recovery.js +456 -0
  113. package/dist/lib/community-native-lifecycle.js +2458 -0
  114. package/dist/lib/community-native-migration.js +339 -0
  115. package/dist/lib/community-native-postgres.js +596 -0
  116. package/dist/lib/community-operation-journal.js +773 -0
  117. package/dist/lib/community-operation-lock.js +596 -0
  118. package/dist/lib/community-release-resolver.js +291 -0
  119. package/dist/lib/community-release-verifier.js +475 -0
  120. package/dist/lib/community-repo-discovery.js +252 -0
  121. package/dist/lib/community-setup-server-env.js +203 -0
  122. package/dist/lib/external-url.js +110 -0
  123. package/dist/lib/official-catalog-gateway.js +108 -0
  124. package/dist/lib/official-catalog.js +364 -0
  125. package/dist/lib/server-bootstrap.js +240 -0
  126. package/dist/lib/setup-agent-assets-bridge.js +176 -0
  127. package/dist/lib/setup-agent-assets-release.js +52 -0
  128. package/dist/lib/setup-external-postgres.js +108 -0
  129. package/dist/lib/setup-init-orchestrator.js +990 -0
  130. package/dist/lib/setup-install-guide.js +52 -0
  131. package/dist/lib/setup-installer-release.js +47 -0
  132. package/dist/lib/setup-local-postgres.js +329 -0
  133. package/dist/lib/setup-official-catalog-bridge.js +148 -0
  134. package/dist/lib/setup-readiness.js +888 -0
  135. package/dist/lib/tui-launcher.js +59 -0
  136. package/dist/main.js +150 -0
  137. package/dist/seeds/agents-md.js +54 -0
  138. package/dist/utils/agent-gateway.js +275 -0
  139. package/dist/utils/agents-md.js +281 -0
  140. package/dist/utils/api.js +255 -0
  141. package/dist/utils/chatv3-session-store.js +185 -0
  142. package/dist/utils/command.js +67 -0
  143. package/dist/utils/community-host-runtime.js +376 -0
  144. package/dist/utils/config.js +1015 -0
  145. package/dist/utils/discussion-workspace.js +552 -0
  146. package/dist/utils/experience-workspace.js +326 -0
  147. package/dist/utils/guide-paths.js +67 -0
  148. package/dist/utils/hosted-sugar.js +56 -0
  149. package/dist/utils/hosted-workspace.js +229 -0
  150. package/dist/utils/memory-workspace.js +537 -0
  151. package/dist/utils/operator-runtime.js +199 -0
  152. package/dist/utils/password-crypto.js +39 -0
  153. package/dist/utils/playbook-workspace.js +479 -0
  154. package/dist/utils/project-context.js +255 -0
  155. package/dist/utils/prompts.js +70 -0
  156. package/dist/utils/refresh-race.js +28 -0
  157. package/dist/utils/repo-config.js +109 -0
  158. package/dist/utils/repo-first-projectman.js +194 -0
  159. package/dist/utils/repo-first-storage.js +13 -0
  160. package/dist/utils/repo-first-view.js +305 -0
  161. package/dist/utils/session-guidance.js +278 -0
  162. package/dist/utils/session-state.js +137 -0
  163. package/dist/utils/table.js +39 -0
  164. package/dist/utils/transient-fs.js +49 -0
  165. package/dist/utils/workspace-root.js +1 -0
  166. package/launchers/aops-cockpit.ps1 +15 -0
  167. package/launchers/aops-cockpit.sh +16 -0
  168. package/launchers/aops-server.ps1 +15 -0
  169. package/launchers/aops-server.sh +16 -0
  170. package/native/tui/win32-x64/aops-tui.exe +0 -0
  171. package/package.json +81 -0
@@ -0,0 +1,134 @@
1
+ import path from 'node:path';
2
+ import { Command } from 'commander';
3
+ import { verifyAndLoadCommunityCatalogReleaseInputs } from '../lib/agent-assets/release-input.js';
4
+ import { createHostedOfficialCatalogAdapterV1 } from '../lib/official-catalog-gateway.js';
5
+ import { buildOfficialCatalogPackageImports, OFFICIAL_CATALOG_SCOPE_V1, OFFICIAL_CATALOG_TOOL_IDS_V1, OfficialCatalogError, reconcileOfficialCatalog, rollbackOfficialCatalog, } from '../lib/official-catalog.js';
6
+ export const OFFICIAL_CATALOG_SETUP_SURFACE_V1 = 'official-catalog-setup-v1';
7
+ function required(value, flag) {
8
+ const normalized = value?.trim();
9
+ if (!normalized)
10
+ throw new OfficialCatalogError('catalog_release_invalid', `${flag} is required.`);
11
+ return normalized;
12
+ }
13
+ function releaseRoot(value) {
14
+ const normalized = required(value, '--from-release');
15
+ if (/\0|\r|\n/.test(normalized) || /^[a-z][a-z0-9+.-]*:\/\//i.test(normalized)) {
16
+ throw new OfficialCatalogError('catalog_release_invalid', '--from-release must be a local verified release directory.');
17
+ }
18
+ return path.resolve(normalized);
19
+ }
20
+ function mode(options) {
21
+ if (options.preview && options.apply) {
22
+ throw new OfficialCatalogError('catalog_release_invalid', '--preview and --apply are mutually exclusive.');
23
+ }
24
+ return options.apply === true ? 'apply' : 'preview';
25
+ }
26
+ async function resolveAdapter(options, dependencies) {
27
+ if (dependencies.adapter)
28
+ return dependencies.adapter;
29
+ return createHostedOfficialCatalogAdapterV1(options);
30
+ }
31
+ function print(command, mutationFree, result) {
32
+ console.log(JSON.stringify({
33
+ schemaVersion: 1,
34
+ surface: OFFICIAL_CATALOG_SETUP_SURFACE_V1,
35
+ command,
36
+ mutationFree,
37
+ scope: OFFICIAL_CATALOG_SCOPE_V1,
38
+ result,
39
+ }, null, 2));
40
+ }
41
+ export async function runOfficialCatalogStatus(options = {}, dependencies = {}) {
42
+ const adapter = await resolveAdapter(options, dependencies);
43
+ const result = await adapter.inspect();
44
+ print('setup.catalog.status', true, result);
45
+ return result;
46
+ }
47
+ export async function runOfficialCatalogReconcile(options = {}, dependencies = {}) {
48
+ const selectedMode = mode(options);
49
+ const loadRelease = dependencies.loadRelease ?? verifyAndLoadCommunityCatalogReleaseInputs;
50
+ const inputs = await loadRelease({
51
+ releaseRoot: releaseRoot(options.fromRelease),
52
+ verificationMode: 'offline',
53
+ });
54
+ const packages = buildOfficialCatalogPackageImports(inputs);
55
+ const adapter = await resolveAdapter(options, dependencies);
56
+ const result = await reconcileOfficialCatalog({
57
+ adapter,
58
+ packages,
59
+ mode: selectedMode,
60
+ idempotencyKey: options.idempotencyKey,
61
+ });
62
+ print('setup.catalog.reconcile', selectedMode === 'preview' || result.kind === 'aops-official-catalog-reconcile-plan-v1', result);
63
+ return result;
64
+ }
65
+ export async function runOfficialCatalogRollback(options = {}, dependencies = {}) {
66
+ const selectedMode = mode(options);
67
+ if (selectedMode === 'apply' && options.confirm !== true) {
68
+ throw new OfficialCatalogError('catalog_release_invalid', 'Rollback requires --apply --confirm.');
69
+ }
70
+ const adapter = await resolveAdapter(options, dependencies);
71
+ const result = await rollbackOfficialCatalog({
72
+ adapter,
73
+ receiptId: required(options.receipt, '--receipt'),
74
+ mode: selectedMode,
75
+ idempotencyKey: options.idempotencyKey,
76
+ });
77
+ print('setup.catalog.rollback', selectedMode === 'preview', result);
78
+ return result;
79
+ }
80
+ function addRemoteOptions(command) {
81
+ return command
82
+ .option('--api-base-url <url>', 'Local or remote AOPS server URL')
83
+ .option('--timeout-ms <ms>', 'Hosted operation timeout', (value) => Number.parseInt(String(value), 10))
84
+ .option('--tenant-id <id>', 'Tenant routing header; catalog scope remains fixed')
85
+ .option('--json', 'Output the stable official-catalog-setup-v1 envelope');
86
+ }
87
+ export function makeOfficialCatalogSetupCommand(dependencies = {}) {
88
+ const command = new Command('catalog')
89
+ .description(`Inspect or reconcile the inert signed AOPS official server catalog in the fixed ${OFFICIAL_CATALOG_SCOPE_V1.slug} scope (server contract: ${OFFICIAL_CATALOG_TOOL_IDS_V1.reconcile})`);
90
+ addRemoteOptions(command.command('status')
91
+ .description('Inspect only the reserved official catalog scope')
92
+ .action(async (options) => {
93
+ await runOfficialCatalogStatus(options, dependencies);
94
+ }));
95
+ addRemoteOptions(command.command('reconcile')
96
+ .description('Preview or apply an append-only reconcile from one verified Community release')
97
+ .requiredOption('--from-release <path>', 'Local signed Community release directory')
98
+ .option('--idempotency-key <key>', 'Explicit replay key; otherwise derived from release and reserved-scope revision')
99
+ .option('--preview', 'Preview only (default)')
100
+ .option('--apply', 'Append versions and update only the reserved current-version map')
101
+ .action(async (options) => {
102
+ await runOfficialCatalogReconcile(options, dependencies);
103
+ }));
104
+ addRemoteOptions(command.command('rollback')
105
+ .description('Restore a prior reserved current-version map without deleting history')
106
+ .requiredOption('--receipt <id>', 'Exact reconcile receipt to restore')
107
+ .option('--idempotency-key <key>', 'Explicit rollback replay key')
108
+ .option('--preview', 'Preview only (default)')
109
+ .option('--apply', 'Apply the receipt-targeted current-version rollback')
110
+ .option('--confirm', 'Confirm the rollback; required with --apply')
111
+ .action(async (options) => {
112
+ await runOfficialCatalogRollback(options, dependencies);
113
+ }));
114
+ command.addHelpText('after', `
115
+ The reserved scope is fixed to ${OFFICIAL_CATALOG_SCOPE_V1.slug}. It is routing
116
+ identity only: official trust comes from the verified Community release and the
117
+ package/release digests persisted in SkillVersion meta. Import is inert and does
118
+ not select a discipline, mutate a mission policy, or touch user/project scopes.
119
+
120
+ Agentspace owns the atomic composite operations and durable receipts. Inspect
121
+ their exact server contracts with:
122
+ aops-cli agent schema --tool ${OFFICIAL_CATALOG_TOOL_IDS_V1.inspect} --summary
123
+ aops-cli agent schema --tool ${OFFICIAL_CATALOG_TOOL_IDS_V1.reconcile} --summary
124
+ aops-cli agent schema --tool ${OFFICIAL_CATALOG_TOOL_IDS_V1.rollback} --summary
125
+
126
+ Examples:
127
+ aops-cli setup catalog status --json
128
+ aops-cli setup catalog reconcile --from-release <path> --preview --json
129
+ aops-cli setup catalog reconcile --from-release <path> --apply --json
130
+ aops-cli setup catalog rollback --receipt <id> --preview --json
131
+ aops-cli setup catalog rollback --receipt <id> --apply --confirm --json
132
+ `);
133
+ return command;
134
+ }
@@ -0,0 +1,84 @@
1
+ import { Command } from 'commander';
2
+ import { applyCommonOptions } from '../utils/command.js';
3
+ import { GUIDE_PATHS } from '../utils/guide-paths.js';
4
+ import { collectRepeatedOption, runPmPlanCreate, runPmPlanGet, runPmPlanList, runPmPlanUpdate, } from './pm/projectman.js';
5
+ function applyPlanContextOptions(cmd) {
6
+ applyCommonOptions(cmd, { withProject: false });
7
+ cmd.option('--scope-id <id>', 'Canonical owner scope override');
8
+ cmd.option('--project-id <id>', 'Project id used to resolve the canonical owner scope');
9
+ cmd.option('--project-name <name>', 'Project name override for repo-aware scope resolution');
10
+ cmd.option('--project-slug <slug>', 'Project slug from repo project registry; hosted-only links write through the hosted gateway');
11
+ cmd.option('--tenant-id <id>', 'Tenant id header (x-tenant-id)');
12
+ cmd.option('--locale <locale>', 'Locale header (x-locale)');
13
+ cmd.option('--fallback-locale <locale>', 'Fallback locale header (x-fallback-locale)');
14
+ cmd.addHelpText('after', `\nGuide:\n ${GUIDE_PATHS.projectman}\n`);
15
+ return cmd;
16
+ }
17
+ function applyWriteOptions(cmd) {
18
+ cmd.option('--preview', 'Return a validated preflight summary without executing the tool');
19
+ cmd.option('--apply', 'Explicitly allow guarded write operations');
20
+ cmd.option('--idempotency-key <key>', 'Optional guarded-write idempotency key');
21
+ return cmd;
22
+ }
23
+ function applyMemoryCadenceOptions(cmd) {
24
+ cmd.option('--write-memory', 'Write an opt-in local memory side-effect after the main repo-first operation succeeds');
25
+ cmd.option('--memory-mode <mode>', 'Memory mode override: kickoff, resume, decision, blocker, closeout, rule');
26
+ cmd.option('--memory-content <text>', 'Optional explicit memory content override');
27
+ cmd.option('--memory-next-action <text>', 'Optional next action for the memory side-effect');
28
+ cmd.option('--memory-validation-state <text>', 'Optional validation state for the memory side-effect');
29
+ return cmd;
30
+ }
31
+ export function makePlanCommand() {
32
+ const cmd = new Command('plan')
33
+ .description('Sprint-backed Projectman implementation plan commands')
34
+ .addHelpText('after', '\nNotes:\n' +
35
+ ' implementation-plan is a facade over Projectman sprint documents; the plan id is the sprint id.\n' +
36
+ ' This command does not create a second plan table or app-local plan store.\n' +
37
+ ' For one-item checklist edits, use `aops-cli pm utask ...` until top-level plan microtask sugar is added.\n');
38
+ applyPlanContextOptions(applyMemoryCadenceOptions(applyWriteOptions(cmd
39
+ .command('create')
40
+ .description('Create a sprint-backed implementation plan for a kanban task')
41
+ .requiredOption('--task <id>', 'Kanban task id')
42
+ .requiredOption('--name <name>', 'Plan name')
43
+ .requiredOption('--goal <text>', 'Plan goal')
44
+ .option('--reference <value>', 'Repeatable reference item', collectRepeatedOption, [])
45
+ .option('--scope-item <value>', 'Repeatable scope item', collectRepeatedOption, [])
46
+ .option('--validation-item <value>', 'Repeatable validation item', collectRepeatedOption, [])
47
+ .option('--notes <text>', 'Optional notes'))).action(async (options) => {
48
+ await runPmPlanCreate(options);
49
+ }));
50
+ applyPlanContextOptions(cmd
51
+ .command('list')
52
+ .description('List sprint-backed implementation plans')
53
+ .option('--task <id>', 'Optional kanban task id filter')
54
+ .option('--name <name>', 'Optional plan name filter')
55
+ .option('--status <value>', 'Optional plan status filter')
56
+ .option('--limit <n>', 'Maximum records to return after filtering')
57
+ .option('--summary', 'Return compact plan rows instead of full sprint plans')
58
+ .option('--include-archived', 'Include plans whose backing sprint has archivedAt set')
59
+ .action(async (options) => {
60
+ await runPmPlanList(options);
61
+ }));
62
+ applyPlanContextOptions(cmd
63
+ .command('get')
64
+ .description('Get a sprint-backed implementation plan by selector')
65
+ .requiredOption('--id <id>', 'Plan selector: full id, 8+ id prefix, slug, or exact name/title')
66
+ .action(async (options) => {
67
+ await runPmPlanGet(options);
68
+ }));
69
+ applyPlanContextOptions(applyMemoryCadenceOptions(applyWriteOptions(cmd
70
+ .command('update')
71
+ .description('Patch a sprint-backed implementation plan, including phases and microtasks')
72
+ .requiredOption('--id <id>', 'Plan id')
73
+ .option('--name <name>', 'Updated plan name')
74
+ .option('--goal <text>', 'Updated plan goal')
75
+ .option('--reference <value>', 'Repeatable reference item patch', collectRepeatedOption, [])
76
+ .option('--scope-item <value>', 'Repeatable scope item patch', collectRepeatedOption, [])
77
+ .option('--validation-item <value>', 'Repeatable validation item patch', collectRepeatedOption, [])
78
+ .option('--notes <text>', 'Updated plan notes')
79
+ .option('--phases-json <json-or-@file>', 'JSON array or @file.json array for nested phase+microtask plan; quote @file values in PowerShell')
80
+ .option('--expected-updated-at <timestamp>', 'Optional optimistic concurrency timestamp from a previous plan snapshot'))).action(async (options) => {
81
+ await runPmPlanUpdate(options);
82
+ }));
83
+ return cmd;
84
+ }
@@ -0,0 +1,415 @@
1
+ import { Command } from 'commander';
2
+ import { logError, logSuccess } from '@aopslabs/xf-cli-ui';
3
+ import { applyCommonOptions, compactPayload, normalizeNonEmpty } from '../utils/command.js';
4
+ import { runExperiencePromote } from './experience.js';
5
+ import { buildProjectPlaybookSet, filterPlaybookRecords, renderPlaybookMarkdown, toPlaybookBrief, } from '../utils/playbook-workspace.js';
6
+ import { invokeHostedToolWithApiState, requireApiState, unwrapHostedToolResult, } from '../utils/agent-gateway.js';
7
+ import { preferProjectNameBinding, resolveOwnerScopeIdFromBinding, resolveOwnerScopeIdFromProjectRecord, resolveProjectBindingContext, } from '../utils/project-context.js';
8
+ const PLAYBOOK_TOOL_ID = 'agentspace.playbook.list';
9
+ function collectRepeatedOption(value, previous = []) {
10
+ const normalized = normalizeNonEmpty(value);
11
+ return normalized ? [...previous, normalized] : previous;
12
+ }
13
+ function parseInteger(value) {
14
+ return Number.parseInt(value, 10);
15
+ }
16
+ function applyProjectSlugOption(cmd) {
17
+ if (cmd.options.some((option) => option.long === '--project-slug'))
18
+ return cmd;
19
+ return cmd.option('--project-slug <slug>', 'Project slug from repo project registry');
20
+ }
21
+ function isRecord(value) {
22
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
23
+ }
24
+ function toStringArray(values) {
25
+ return Array.isArray(values)
26
+ ? values.map((entry) => normalizeNonEmpty(entry)).filter((entry) => Boolean(entry))
27
+ : [];
28
+ }
29
+ function buildResolvedContextRecord(context) {
30
+ return compactPayload({
31
+ repoRoot: context.repoRoot,
32
+ configPath: context.configPath,
33
+ configFound: context.configFound,
34
+ scopeId: context.scopeId,
35
+ projectId: context.projectId,
36
+ projectName: context.projectName,
37
+ });
38
+ }
39
+ function buildEnvelope(params) {
40
+ return {
41
+ command: params.command,
42
+ toolId: PLAYBOOK_TOOL_ID,
43
+ resolvedContext: buildResolvedContextRecord(params.resolvedContext),
44
+ input: params.input,
45
+ result: params.result,
46
+ };
47
+ }
48
+ function emitResult(options, message, envelope, plainResult) {
49
+ if (options.json) {
50
+ console.log(JSON.stringify(envelope, null, 2));
51
+ return;
52
+ }
53
+ logSuccess(message);
54
+ console.log(JSON.stringify(plainResult, null, 2));
55
+ }
56
+ function buildGatewayOptions(options, resolvedContext) {
57
+ return {
58
+ ...options,
59
+ ...preferProjectNameBinding(resolvedContext),
60
+ };
61
+ }
62
+ async function resolvePlaybookContext(options) {
63
+ const resolved = await resolveProjectBindingContext(options, { requireProject: false });
64
+ const scopeId = normalizeNonEmpty(options.scopeId) ?? resolveOwnerScopeIdFromBinding(resolved);
65
+ return { ...resolved, scopeId };
66
+ }
67
+ async function hydrateProjectScopeContext(apiState, options, resolvedContext) {
68
+ const projectId = normalizeNonEmpty(resolvedContext.projectId);
69
+ if (!projectId) {
70
+ return resolvedContext;
71
+ }
72
+ if (normalizeNonEmpty(resolvedContext.scopeId) === projectId && normalizeNonEmpty(resolvedContext.projectName)) {
73
+ return resolvedContext;
74
+ }
75
+ try {
76
+ const payload = await invokeHostedToolWithApiState(apiState, {
77
+ ...buildGatewayOptions(options, resolvedContext),
78
+ toolId: 'agentspace.project.get-by-id',
79
+ input: { id: projectId },
80
+ });
81
+ const result = unwrapHostedToolResult(payload);
82
+ const project = isRecord(result) && Object.prototype.hasOwnProperty.call(result, 'data')
83
+ ? result.data
84
+ : result;
85
+ if (!isRecord(project)) {
86
+ return resolvedContext;
87
+ }
88
+ const scopeId = resolveOwnerScopeIdFromProjectRecord(project, resolvedContext.scopeId ?? projectId);
89
+ const projectName = normalizeNonEmpty(project.name) ?? resolvedContext.projectName;
90
+ return {
91
+ ...resolvedContext,
92
+ scopeId: scopeId ?? resolvedContext.scopeId,
93
+ projectName,
94
+ };
95
+ }
96
+ catch {
97
+ return resolvedContext;
98
+ }
99
+ }
100
+ function extractHostedRows(result) {
101
+ const data = isRecord(result) && Object.prototype.hasOwnProperty.call(result, 'data') ? result.data : result;
102
+ if (Array.isArray(data))
103
+ return data.filter(isRecord);
104
+ if (Array.isArray(result))
105
+ return result.filter(isRecord);
106
+ return [];
107
+ }
108
+ function hostedProjectionToPlaybookRecord(row) {
109
+ const id = normalizeNonEmpty(row.id) ?? normalizeNonEmpty(row.playbookId);
110
+ return {
111
+ id,
112
+ playbookId: normalizeNonEmpty(row.playbookId) ?? id,
113
+ title: normalizeNonEmpty(row.title) ?? id,
114
+ memoryItemId: normalizeNonEmpty(row.memoryItemId) ?? normalizeNonEmpty(row.sourceMemoryItemId),
115
+ kind: normalizeNonEmpty(row.kind),
116
+ durability: normalizeNonEmpty(row.durability),
117
+ scope: normalizeNonEmpty(row.scope),
118
+ area: normalizeNonEmpty(row.area),
119
+ appliesWhen: normalizeNonEmpty(row.appliesWhen),
120
+ steps: toStringArray(row.steps),
121
+ enforcement: normalizeNonEmpty(row.enforcement),
122
+ confidence: normalizeNonEmpty(row.confidence),
123
+ reviewState: normalizeNonEmpty(row.reviewState),
124
+ supersedes: normalizeNonEmpty(row.supersedes),
125
+ promotedFromExperienceId: normalizeNonEmpty(row.promotedFromExperienceId),
126
+ sessionContext: row.sessionContext,
127
+ sourceType: normalizeNonEmpty(row.sourceType),
128
+ sourceId: normalizeNonEmpty(row.sourceId),
129
+ sourceRefs: Array.isArray(row.sourceRefs) ? row.sourceRefs : undefined,
130
+ content: normalizeNonEmpty(row.content),
131
+ tags: toStringArray(row.tags),
132
+ updatedAt: normalizeNonEmpty(row.updatedAt),
133
+ createdAt: normalizeNonEmpty(row.createdAt),
134
+ };
135
+ }
136
+ async function loadPlaybooks(apiState, context, options) {
137
+ const input = compactPayload({
138
+ filter: compactPayload({
139
+ scopeId: context.scopeId,
140
+ scopeResolution: 'cascade',
141
+ projectId: context.projectId,
142
+ }),
143
+ });
144
+ const payload = await invokeHostedToolWithApiState(apiState, {
145
+ ...buildGatewayOptions(options, context),
146
+ tenantId: options.tenantId,
147
+ locale: options.locale,
148
+ fallbackLocale: options.fallbackLocale,
149
+ timeoutMs: options.timeoutMs,
150
+ apiBaseUrl: options.apiBaseUrl,
151
+ accessToken: options.accessToken,
152
+ refreshToken: options.refreshToken,
153
+ toolId: PLAYBOOK_TOOL_ID,
154
+ input,
155
+ });
156
+ const result = unwrapHostedToolResult(payload);
157
+ return extractHostedRows(result)
158
+ .map(hostedProjectionToPlaybookRecord)
159
+ .sort((left, right) => (right.updatedAt ?? right.createdAt ?? '').localeCompare(left.updatedAt ?? left.createdAt ?? ''));
160
+ }
161
+ function emitPlaybookCommandError(params) {
162
+ const message = params.error instanceof Error ? params.error.message : String(params.error);
163
+ if (params.options.json) {
164
+ console.log(JSON.stringify({
165
+ command: params.command,
166
+ toolId: PLAYBOOK_TOOL_ID,
167
+ resolvedContext: params.resolvedContext ? buildResolvedContextRecord(params.resolvedContext) : {},
168
+ input: params.input ?? {},
169
+ result: { error: { message } },
170
+ }, null, 2));
171
+ }
172
+ else {
173
+ logError(message);
174
+ }
175
+ process.exitCode = 1;
176
+ }
177
+ export async function runPlaybookList(options = {}) {
178
+ let context;
179
+ let apiState;
180
+ try {
181
+ apiState = await requireApiState(options);
182
+ if (!apiState)
183
+ return;
184
+ context = await resolvePlaybookContext(options);
185
+ context = await hydrateProjectScopeContext(apiState, options, context);
186
+ }
187
+ catch (error) {
188
+ logError(error instanceof Error ? error.message : String(error));
189
+ process.exitCode = 1;
190
+ return;
191
+ }
192
+ try {
193
+ const playbooks = filterPlaybookRecords(await loadPlaybooks(apiState, context, options), {
194
+ scope: options.scope,
195
+ area: options.area,
196
+ durability: options.durability,
197
+ reviewState: options.reviewState,
198
+ tag: options.tag,
199
+ limit: options.limit,
200
+ });
201
+ const result = {
202
+ data: playbooks.map((record) => ({ ...record, brief: toPlaybookBrief(record) })),
203
+ count: playbooks.length,
204
+ filters: compactPayload({
205
+ scope: options.scope,
206
+ area: options.area,
207
+ durability: options.durability,
208
+ reviewState: options.reviewState,
209
+ tag: options.tag,
210
+ limit: options.limit,
211
+ }),
212
+ };
213
+ emitResult(options, 'Hosted playbook list completed.', buildEnvelope({ command: 'playbook.list', resolvedContext: context, input: compactPayload({ hostedFirst: true, ...options }), result }), result);
214
+ }
215
+ catch (error) {
216
+ emitPlaybookCommandError({ options, command: 'playbook.list', resolvedContext: context, error });
217
+ }
218
+ }
219
+ export async function runPlaybookGet(options = {}) {
220
+ let context;
221
+ let apiState;
222
+ try {
223
+ const id = normalizeNonEmpty(options.id);
224
+ if (!id)
225
+ throw new Error('Provide --id.');
226
+ apiState = await requireApiState(options);
227
+ if (!apiState)
228
+ return;
229
+ context = await resolvePlaybookContext(options);
230
+ context = await hydrateProjectScopeContext(apiState, options, context);
231
+ }
232
+ catch (error) {
233
+ logError(error instanceof Error ? error.message : String(error));
234
+ process.exitCode = 1;
235
+ return;
236
+ }
237
+ try {
238
+ const id = normalizeNonEmpty(options.id);
239
+ const [record] = filterPlaybookRecords(await loadPlaybooks(apiState, context, options), { id });
240
+ if (!record)
241
+ throw new Error('Hosted playbook was not found.');
242
+ const result = { data: { ...record, brief: toPlaybookBrief(record) } };
243
+ emitResult(options, 'Hosted playbook loaded.', buildEnvelope({ command: 'playbook.get', resolvedContext: context, input: { id, hostedFirst: true }, result }), result);
244
+ }
245
+ catch (error) {
246
+ emitPlaybookCommandError({ options, command: 'playbook.get', resolvedContext: context, input: compactPayload({ id: normalizeNonEmpty(options.id) }), error });
247
+ }
248
+ }
249
+ export async function runPlaybookShow(options = {}) {
250
+ let context;
251
+ let apiState;
252
+ try {
253
+ const id = normalizeNonEmpty(options.id);
254
+ if (!id)
255
+ throw new Error('Provide --id.');
256
+ apiState = await requireApiState(options);
257
+ if (!apiState)
258
+ return;
259
+ context = await resolvePlaybookContext(options);
260
+ context = await hydrateProjectScopeContext(apiState, options, context);
261
+ }
262
+ catch (error) {
263
+ logError(error instanceof Error ? error.message : String(error));
264
+ process.exitCode = 1;
265
+ return;
266
+ }
267
+ try {
268
+ const id = normalizeNonEmpty(options.id);
269
+ const [record] = filterPlaybookRecords(await loadPlaybooks(apiState, context, options), { id });
270
+ if (!record)
271
+ throw new Error('Hosted playbook was not found.');
272
+ const markdown = renderPlaybookMarkdown(record);
273
+ const result = { data: { ...record, brief: toPlaybookBrief(record) }, markdown };
274
+ if (options.json) {
275
+ console.log(JSON.stringify(buildEnvelope({
276
+ command: 'playbook.show',
277
+ resolvedContext: context,
278
+ input: { id, hostedFirst: true },
279
+ result,
280
+ }), null, 2));
281
+ return;
282
+ }
283
+ process.stdout.write(markdown);
284
+ }
285
+ catch (error) {
286
+ emitPlaybookCommandError({ options, command: 'playbook.show', resolvedContext: context, input: compactPayload({ id: normalizeNonEmpty(options.id) }), error });
287
+ }
288
+ }
289
+ export async function runPlaybookProjectSet(options = {}) {
290
+ let context;
291
+ let apiState;
292
+ try {
293
+ apiState = await requireApiState(options);
294
+ if (!apiState)
295
+ return;
296
+ context = await resolvePlaybookContext(options);
297
+ context = await hydrateProjectScopeContext(apiState, options, context);
298
+ }
299
+ catch (error) {
300
+ logError(error instanceof Error ? error.message : String(error));
301
+ process.exitCode = 1;
302
+ return;
303
+ }
304
+ try {
305
+ const records = filterPlaybookRecords(await loadPlaybooks(apiState, context, options), {
306
+ scope: normalizeNonEmpty(options.scope) ?? 'project',
307
+ area: options.area,
308
+ durability: options.durability,
309
+ reviewState: options.reviewState,
310
+ tag: options.tag,
311
+ limit: options.limit,
312
+ });
313
+ const result = buildProjectPlaybookSet(records, {
314
+ projectSlug: context.projectSlug,
315
+ reviewState: options.reviewState ? [options.reviewState] : undefined,
316
+ durability: options.durability,
317
+ limit: options.limit,
318
+ });
319
+ emitResult(options, 'Hosted project playbook set loaded.', buildEnvelope({
320
+ command: 'playbook.project-set',
321
+ resolvedContext: context,
322
+ input: compactPayload({ hostedFirst: true, scope: options.scope ?? 'project', reviewState: options.reviewState, limit: options.limit }),
323
+ result,
324
+ }), result);
325
+ }
326
+ catch (error) {
327
+ emitPlaybookCommandError({ options, command: 'playbook.project-set', resolvedContext: context, error });
328
+ }
329
+ }
330
+ export async function runPlaybookPromote(options = {}) {
331
+ await runExperiencePromote({
332
+ ...options,
333
+ asPlaybook: true,
334
+ playbookScope: normalizeNonEmpty(options.scope) ?? normalizeNonEmpty(options.playbookScope),
335
+ playbookArea: normalizeNonEmpty(options.area) ?? normalizeNonEmpty(options.playbookArea),
336
+ });
337
+ }
338
+ export function makePlaybookCommand() {
339
+ const cmd = new Command('playbook')
340
+ .description('List reviewed playbooks projected from hosted Agentspace memory rules/constraints (server-first)');
341
+ applyCommonOptions(cmd, { withAuth: false, withProject: true, withYes: true, withJson: true });
342
+ applyProjectSlugOption(cmd);
343
+ cmd.addHelpText('after', `
344
+ Playbooks are prescriptive methods/patterns/rules stored as hosted memory items
345
+ and read server-first through the hosted playbook.list projection:
346
+ Source of truth (hosted):
347
+ agentspace.playbook.list (projects memory items kind=rule|constraint with meta.playbook)
348
+ Storage contract:
349
+ kind=rule|constraint, tags include playbook/playbook-scope:*, meta.playbook
350
+ Local .aops is a read-only cache only; it is never read as truth or rewritten.
351
+
352
+ Experience stays descriptive. Promote reviewed experience into a playbook explicitly:
353
+ aops-cli experience promote --id <experience-id> --as-playbook --playbook-area backend --apply --json
354
+ Operator alias:
355
+ aops-cli playbook promote --id <experience-id> --area backend --review-state accepted --apply --json
356
+ `);
357
+ const list = cmd.command('list')
358
+ .description('List hosted playbook projections')
359
+ .option('--scope <scope>', 'Filter by playbook scope: session or project')
360
+ .option('--area <area>', 'Filter by playbook area; repeatable', collectRepeatedOption, [])
361
+ .option('--durability <durability>', 'Filter by memory durability; repeatable', collectRepeatedOption, [])
362
+ .option('--review-state <state>', 'Filter by review state')
363
+ .option('--tag <tag>', 'Filter by tag; repeatable', collectRepeatedOption, [])
364
+ .option('--limit <n>', 'Limit result count', parseInteger)
365
+ .action((options) => runPlaybookList({ ...cmd.opts(), ...options }));
366
+ applyCommonOptions(list, { withAuth: false, withProject: true, withYes: true, withJson: true });
367
+ applyProjectSlugOption(list);
368
+ const get = cmd.command('get')
369
+ .description('Get one hosted playbook projection')
370
+ .requiredOption('--id <id>', 'Playbook id, memory id, or title prefix')
371
+ .action((options) => runPlaybookGet({ ...cmd.opts(), ...options }));
372
+ applyCommonOptions(get, { withAuth: false, withProject: true, withYes: true, withJson: true });
373
+ applyProjectSlugOption(get);
374
+ const show = cmd.command('show')
375
+ .description('Render one hosted playbook as agent-readable markdown')
376
+ .requiredOption('--id <id>', 'Playbook id, memory id, or title prefix')
377
+ .action((options) => runPlaybookShow({ ...cmd.opts(), ...options }));
378
+ applyCommonOptions(show, { withAuth: false, withProject: true, withYes: true, withJson: true });
379
+ applyProjectSlugOption(show);
380
+ const projectSet = cmd.command('project-set')
381
+ .description('Load accepted sticky/durable project-scope playbooks for start/resume guidance')
382
+ .option('--scope <scope>', 'Playbook scope (default: project)', 'project')
383
+ .option('--area <area>', 'Filter by playbook area; repeatable', collectRepeatedOption, [])
384
+ .option('--durability <durability>', 'Filter by memory durability; repeatable', collectRepeatedOption, [])
385
+ .option('--review-state <state>', 'Filter by review state (default: accepted)', 'accepted')
386
+ .option('--tag <tag>', 'Filter by tag; repeatable', collectRepeatedOption, [])
387
+ .option('--limit <n>', 'Limit result count (default: 8)', parseInteger)
388
+ .action((options) => runPlaybookProjectSet({ ...cmd.opts(), ...options }));
389
+ applyCommonOptions(projectSet, { withAuth: false, withProject: true, withYes: true, withJson: true });
390
+ applyProjectSlugOption(projectSet);
391
+ const promote = cmd.command('promote')
392
+ .description('Promote reviewed experience into a playbook memory rule/constraint')
393
+ .requiredOption('--id <experience-id>', 'Experience id')
394
+ .option('--kind <kind>', 'Memory kind: rule or constraint (default: rule)')
395
+ .option('--durability <durability>', 'Memory durability: durable or sticky (default: durable)')
396
+ .option('--content <text>', 'Reviewed promoted playbook content')
397
+ .option('--tag <tag>', 'Extra memory tag; repeatable', collectRepeatedOption, [])
398
+ .option('--playbook-id <id>', 'Stable playbook id (default: experience id)')
399
+ .option('--scope <scope>', 'Playbook scope: session or project (default: project)')
400
+ .option('--playbook-scope <scope>', 'Compatibility alias for --scope')
401
+ .option('--area <area>', 'Playbook area tag, such as backend or hexagen')
402
+ .option('--playbook-area <area>', 'Compatibility alias for --area')
403
+ .option('--applies-when <text>', 'When this playbook should be applied')
404
+ .option('--step <text>', 'Playbook step; repeatable (default: experience commands)', collectRepeatedOption, [])
405
+ .option('--enforcement <level>', 'Playbook enforcement: advisory | soft-preflight | strict-opt-in')
406
+ .option('--review-state <state>', 'Playbook review state: proposed | accepted | superseded | archived')
407
+ .option('--supersedes <id>', 'Older playbook id this one supersedes')
408
+ .option('--preview', 'Preview the hosted promotion without applying')
409
+ .option('--apply', 'Write the memory item')
410
+ .option('--idempotency-key <key>', 'Idempotency key for the hosted write')
411
+ .action((options) => runPlaybookPromote({ ...cmd.opts(), ...options }));
412
+ applyCommonOptions(promote, { withAuth: false, withProject: true, withYes: true, withJson: true });
413
+ applyProjectSlugOption(promote);
414
+ return cmd;
415
+ }
@@ -0,0 +1,6 @@
1
+ import { resolveProjectBindingContext, } from '../../utils/project-context.js';
2
+ export async function resolvePmContext(options, params = {}) {
3
+ return resolveProjectBindingContext(options, {
4
+ requireProject: params.requireProject !== false,
5
+ });
6
+ }