@popoverai/dotrequirements 0.25.0 → 0.26.1

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 (103) hide show
  1. package/dist/cli.js +8 -1
  2. package/dist/codebase-to-spec/area-name.d.ts +13 -0
  3. package/dist/codebase-to-spec/area-name.js +18 -0
  4. package/dist/codebase-to-spec/cache.d.ts +31 -0
  5. package/dist/codebase-to-spec/cache.js +29 -1
  6. package/dist/codebase-to-spec/compose.d.ts +7 -4
  7. package/dist/codebase-to-spec/compose.js +10 -21
  8. package/dist/codebase-to-spec/dispatch.js +1 -1
  9. package/dist/codebase-to-spec/present.d.ts +9 -0
  10. package/dist/codebase-to-spec/present.js +30 -4
  11. package/dist/codebase-to-spec/prompts/planner-initial.d.ts +3 -2
  12. package/dist/codebase-to-spec/prompts/planner-initial.js +3 -2
  13. package/dist/codebase-to-spec/renumber.d.ts +52 -0
  14. package/dist/codebase-to-spec/renumber.js +105 -0
  15. package/dist/codebase-to-spec/schemas.d.ts +35 -240
  16. package/dist/codebase-to-spec/schemas.js +5 -173
  17. package/dist/codebase-to-spec/skill-install.d.ts +16 -6
  18. package/dist/codebase-to-spec/skill-install.js +109 -47
  19. package/dist/codebase-to-spec/version-check.d.ts +31 -0
  20. package/dist/codebase-to-spec/version-check.js +56 -0
  21. package/dist/commands/ai-setup.d.ts +12 -1
  22. package/dist/commands/ai-setup.js +65 -33
  23. package/dist/commands/codebase-to-spec/dispatch-editor.js +1 -1
  24. package/dist/commands/codebase-to-spec/dispatch-spec.js +1 -1
  25. package/dist/commands/codebase-to-spec/index.js +7 -103
  26. package/dist/commands/codebase-to-spec/pack.d.ts +4 -0
  27. package/dist/commands/codebase-to-spec/pack.js +25 -1
  28. package/dist/commands/codebase-to-spec/present-orchestrator.d.ts +3 -1
  29. package/dist/commands/codebase-to-spec/present-orchestrator.js +11 -4
  30. package/dist/commands/init.js +6 -1
  31. package/dist/commands/link-resolution.d.ts +79 -0
  32. package/dist/commands/link-resolution.js +141 -0
  33. package/dist/commands/link.d.ts +14 -4
  34. package/dist/commands/link.js +369 -16
  35. package/dist/commands/pull.js +19 -2
  36. package/dist/commands/push.js +36 -2
  37. package/dist/convex.d.ts +5 -3
  38. package/dist/convex.js +5 -3
  39. package/dist/harness/cache.d.ts +0 -14
  40. package/dist/harness/cache.js +1 -41
  41. package/dist/harness/finalize.js +2 -2
  42. package/dist/harness/prepare.js +1 -3
  43. package/dist/harness/requirementsLoader.d.ts +3 -3
  44. package/dist/harness/requirementsLoader.js +13 -8
  45. package/dist/mcp/handlers/authoring.d.ts +5 -5
  46. package/dist/mcp/handlers/authoring.js +9 -9
  47. package/dist/mcp/handlers/push.d.ts +2 -2
  48. package/dist/mcp/handlers/push.js +36 -3
  49. package/dist/mcp/handlers/review.d.ts +4 -4
  50. package/dist/mcp/handlers/review.js +4 -4
  51. package/dist/mcp/handlers/search.d.ts +1 -1
  52. package/dist/mcp/handlers/search.js +1 -1
  53. package/dist/mcp/index.js +29 -0
  54. package/dist/push/core.d.ts +18 -0
  55. package/dist/push/core.js +70 -3
  56. package/dist/push/index.d.ts +1 -1
  57. package/dist/push/index.js +1 -1
  58. package/dist/schema/parser-core.js +5 -1
  59. package/dist/schema/parser.js +5 -1
  60. package/dist/schema/run-marker.d.ts +38 -0
  61. package/dist/schema/run-marker.js +138 -0
  62. package/dist/schema/schemas.d.ts +12 -0
  63. package/dist/schema/schemas.js +1 -0
  64. package/dist/templates/agents/cts-worker.md +1 -1
  65. package/dist/templates/skills/codebase-to-spec/SKILL.md +39 -11
  66. package/dist/templates/workflows/specify-codebase.js +4 -2
  67. package/dist/utils/own-package.d.ts +10 -0
  68. package/dist/utils/own-package.js +13 -0
  69. package/dist/utils/project-selector.d.ts +5 -0
  70. package/dist/utils/project-selector.js +4 -0
  71. package/package.json +2 -2
  72. package/dist/codebase-to-spec/edit-loop.d.ts +0 -54
  73. package/dist/codebase-to-spec/edit-loop.js +0 -195
  74. package/dist/codebase-to-spec/editor.d.ts +0 -54
  75. package/dist/codebase-to-spec/editor.js +0 -74
  76. package/dist/codebase-to-spec/fan-out.d.ts +0 -63
  77. package/dist/codebase-to-spec/fan-out.js +0 -215
  78. package/dist/codebase-to-spec/outline-review-loop.d.ts +0 -51
  79. package/dist/codebase-to-spec/outline-review-loop.js +0 -187
  80. package/dist/codebase-to-spec/planner.d.ts +0 -41
  81. package/dist/codebase-to-spec/planner.js +0 -76
  82. package/dist/codebase-to-spec/prompts/outline-reviewer.d.ts +0 -12
  83. package/dist/codebase-to-spec/prompts/outline-reviewer.js +0 -89
  84. package/dist/codebase-to-spec/slice.d.ts +0 -49
  85. package/dist/codebase-to-spec/slice.js +0 -111
  86. package/dist/codebase-to-spec/specifier.d.ts +0 -60
  87. package/dist/codebase-to-spec/specifier.js +0 -85
  88. package/dist/codebase-to-spec/summary.d.ts +0 -51
  89. package/dist/codebase-to-spec/summary.js +0 -183
  90. package/dist/commands/codebase-to-spec/compose.d.ts +0 -14
  91. package/dist/commands/codebase-to-spec/compose.js +0 -57
  92. package/dist/commands/codebase-to-spec/edit-loop.d.ts +0 -16
  93. package/dist/commands/codebase-to-spec/edit-loop.js +0 -83
  94. package/dist/commands/codebase-to-spec/fan-out.d.ts +0 -19
  95. package/dist/commands/codebase-to-spec/fan-out.js +0 -77
  96. package/dist/commands/codebase-to-spec/plan-loop.d.ts +0 -26
  97. package/dist/commands/codebase-to-spec/plan-loop.js +0 -105
  98. package/dist/commands/codebase-to-spec/present.d.ts +0 -26
  99. package/dist/commands/codebase-to-spec/present.js +0 -97
  100. package/dist/commands/codebase-to-spec/run.d.ts +0 -20
  101. package/dist/commands/codebase-to-spec/run.js +0 -86
  102. package/dist/commands/codebase-to-spec/specify-area.d.ts +0 -18
  103. package/dist/commands/codebase-to-spec/specify-area.js +0 -82
@@ -5,30 +5,23 @@
5
5
  * be invoked granularly (e.g., `dotrequirements cts pack`) per CTS-CLI-2.
6
6
  */
7
7
  import { installCtsAbortHandlers } from "../../codebase-to-spec/progress.js";
8
- import { composeCommand } from "./compose.js";
9
8
  import { composeOrchestratorCommand } from "./compose-orchestrator.js";
10
9
  import { dispatchContextCommand } from "./dispatch-context.js";
11
10
  import { dispatchEditorCommand } from "./dispatch-editor.js";
12
11
  import { dispatchPlannerCommand } from "./dispatch-planner.js";
13
12
  import { dispatchSpecCommand } from "./dispatch-spec.js";
14
- import { editLoopCommand } from "./edit-loop.js";
15
- import { fanOutCommand } from "./fan-out.js";
16
13
  import { packCommand } from "./pack.js";
17
- import { planLoopCommand } from "./plan-loop.js";
18
- import { presentCommand } from "./present.js";
19
14
  import { presentOrchestratorCommand } from "./present-orchestrator.js";
20
- import { runCommand } from "./run.js";
21
15
  import { skillInstallCommand } from "./skill-install.js";
22
- import { specifyAreaCommand } from "./specify-area.js";
23
16
  import { styleCheckCommand } from "./style-check.js";
24
17
  import { validateCommand } from "./validate.js";
25
18
  /**
26
19
  * Marker used to hide a command from `--help` output without removing it.
27
- * Used for dark-ship: the conversational orchestrator's plumbing commands and
28
- * the skill-install entry point ship in the npm package but aren't surfaced
29
- * in help listings or documentation. They remain fully invocable (the
30
- * orchestrator skill calls them by name) — they just don't pollute the
31
- * user-facing CLI surface until we flip the default path in Phase 6.
20
+ * The conversational orchestrator's plumbing commands (the `dispatch-*` payload
21
+ * builders and the `-orchestrator` compose/present variants) and the
22
+ * skill-install entry point ship in the npm package but aren't surfaced in help
23
+ * listings — they're invoked by name by the orchestrator skill and workflow,
24
+ * not by users, so they stay out of the user-facing CLI surface.
32
25
  */
33
26
  const HIDDEN = { hidden: true };
34
27
  export function registerCodebaseToSpec(program) {
@@ -37,9 +30,8 @@ export function registerCodebaseToSpec(program) {
37
30
  .alias("cts")
38
31
  .description("Generate dotrequirements behavioral specifications from a codebase");
39
32
  // Install SIGTERM/SIGINT handlers for every cts subcommand so an interrupted
40
- // pipeline emits a structured `pipeline/aborted` line before exiting
41
- // (CTS-OBSERVE-1.5). preAction fires after argv parsing and before the
42
- // subcommand action runs.
33
+ // stage emits a structured `pipeline/aborted` line before exiting. preAction
34
+ // fires after argv parsing and before the subcommand action runs.
43
35
  cts.hook("preAction", () => {
44
36
  installCtsAbortHandlers();
45
37
  });
@@ -52,31 +44,6 @@ export function registerCodebaseToSpec(program) {
52
44
  .option("--budget <tokens>", "Override the working-context budget (in tokens)", (v) => parseInt(v, 10))
53
45
  .option("--ignore-requirements", "Exclude `.requirements/**` from the pack (use when testing cts against a codebase whose existing requirements should not influence the output)")
54
46
  .action((opts) => packCommand({ ...opts, budgetTokens: opts.budget }));
55
- cts
56
- .command("plan-loop")
57
- .description("Run the planner + outline review loop on the cached pack")
58
- .option("--max-turns <n>", "Hard cap on review turns", (v) => parseInt(v, 10))
59
- .option("--model <name>", "Override the model used by the planner and reviewer")
60
- .action((opts) => planLoopCommand({
61
- maxTurns: opts.maxTurns,
62
- model: opts.model,
63
- }));
64
- cts
65
- .command("specify-area <name>")
66
- .description("Run the specifier for one named area from the cached outline")
67
- .option("--model <name>", "Override the model used by the specifier")
68
- .action((name, opts) => specifyAreaCommand(name, {
69
- model: opts.model,
70
- }));
71
- cts
72
- .command("fan-out")
73
- .description("Run all specifiers in parallel for areas in the cached outline")
74
- .option("-c, --concurrency <n>", "Max concurrent specifiers", (v) => parseInt(v, 10))
75
- .option("--model <name>", "Override the model used by specifiers")
76
- .action((opts) => fanOutCommand({
77
- concurrency: opts.concurrency,
78
- model: opts.model,
79
- }));
80
47
  cts
81
48
  .command("validate <partial-path>")
82
49
  .description("Validate the schema/syntax of a partial-spec file (deterministic, offline)")
@@ -86,69 +53,6 @@ export function registerCodebaseToSpec(program) {
86
53
  .description("Run a local style-check on a partial-spec file (severity-categorized feedback)")
87
54
  .option("--model <name>", "Override the model used for the check")
88
55
  .action((partialPath, opts) => styleCheckCommand(partialPath, { model: opts.model }));
89
- cts
90
- .command("compose")
91
- .description("Assemble per-area partials into a single composed spec")
92
- .action(() => composeCommand());
93
- cts
94
- .command("edit-loop")
95
- .description("Run the spec reviewer + editor loop on the composed spec")
96
- .option("--max-turns <n>", "Hard cap on edit-loop turns", (v) => parseInt(v, 10))
97
- .option("--model <name>", "Override the model used by the reviewer and editor")
98
- .action((opts) => editLoopCommand({
99
- maxTurns: opts.maxTurns,
100
- model: opts.model,
101
- }));
102
- cts
103
- .command("present")
104
- .description("Write the final spec to .requirements/ and emit a pipeline summary")
105
- .option("--interactive", "Force interactive mode (override TTY detection)")
106
- .option("--non-interactive", "Force non-interactive mode (override TTY detection)")
107
- .option("--overwrite", "Replace existing .requirements/ files without prompting")
108
- .option("--skip-existing", "Leave existing .requirements/ files untouched")
109
- .option("--ignore-requirements", "Write output to `.requirements/cts/` instead of `.requirements/` (use together with `cts pack --ignore-requirements` when isolating cts from an existing requirements directory)")
110
- .action((opts) => {
111
- const forceMode = opts.interactive
112
- ? "interactive"
113
- : opts.nonInteractive
114
- ? "non-interactive"
115
- : undefined;
116
- return presentCommand({
117
- forceMode,
118
- overwrite: opts.overwrite,
119
- skipExisting: opts.skipExisting,
120
- ignoreRequirements: opts.ignoreRequirements,
121
- });
122
- });
123
- cts
124
- .command("run")
125
- .description("Run the full pipeline (pack → plan → specify → compose → edit → present)")
126
- .option("-s, --scope <path>", "Limit to files under this path (subdir of project root)")
127
- .option("--fresh", "Clear the cache before running")
128
- .option("--budget <tokens>", "Override the working-context budget (in tokens)", (v) => parseInt(v, 10))
129
- .option("--max-turns <n>", "Hard cap on outline review turns", (v) => parseInt(v, 10))
130
- .option("--model <name>", "Override the model used by agent calls")
131
- .option("--interactive", "Force interactive mode for the present stage")
132
- .option("--non-interactive", "Force non-interactive mode for the present stage")
133
- .option("--overwrite", "Replace existing .requirements/ files without prompting")
134
- .option("--skip-existing", "Leave existing .requirements/ files untouched")
135
- .option("--ignore-requirements", "Exclude `.requirements/**` from the pack and write output to `.requirements/cts/` instead of `.requirements/` (use when testing cts against a codebase whose existing requirements should not influence the output)")
136
- .action((opts) => {
137
- const forceMode = opts.interactive
138
- ? "interactive"
139
- : opts.nonInteractive
140
- ? "non-interactive"
141
- : undefined;
142
- return runCommand({
143
- ...opts,
144
- budgetTokens: opts.budget,
145
- maxTurns: opts.maxTurns,
146
- forceMode,
147
- overwrite: opts.overwrite,
148
- skipExisting: opts.skipExisting,
149
- ignoreRequirements: opts.ignoreRequirements,
150
- });
151
- });
152
56
  cts
153
57
  .command("dispatch-context <dispatch-id>", HIDDEN)
154
58
  .description("Return the composed prompt for a worker dispatch as JSON on stdout (run by cts-worker subagents to fetch their dispatch prompt)")
@@ -7,8 +7,10 @@
7
7
  * - CTS-CLI-3: Pipeline refuses to spec a codebase that exceeds the working context budget
8
8
  * - CTS-PACK-1: Pack stage produces compressed and uncompressed views of the codebase
9
9
  * - CTS-PACK-2: Engineer can extend the ignore list per project
10
+ * - CTS-PACK-4: Pack surfaces when a newer CLI version is available
10
11
  * - CTS-OBSERVE-1: CLI emits structured progress as the pipeline runs
11
12
  */
13
+ import { checkForNewerVersion } from "../../codebase-to-spec/version-check.js";
12
14
  export interface PackOptions {
13
15
  scope?: string;
14
16
  /**
@@ -28,6 +30,8 @@ export interface PackOptions {
28
30
  progress?: import("../../codebase-to-spec/progress.js").ProgressEmitter;
29
31
  /** Override the project root, mainly for tests. */
30
32
  projectRoot?: string;
33
+ /** Override the registry version check, mainly for tests. */
34
+ checkVersion?: typeof checkForNewerVersion;
31
35
  }
32
36
  export declare function packCommand(rawOptions?: PackOptions): Promise<void>;
33
37
  //# sourceMappingURL=pack.d.ts.map
@@ -7,19 +7,25 @@
7
7
  * - CTS-CLI-3: Pipeline refuses to spec a codebase that exceeds the working context budget
8
8
  * - CTS-PACK-1: Pack stage produces compressed and uncompressed views of the codebase
9
9
  * - CTS-PACK-2: Engineer can extend the ignore list per project
10
+ * - CTS-PACK-4: Pack surfaces when a newer CLI version is available
10
11
  * - CTS-OBSERVE-1: CLI emits structured progress as the pipeline runs
11
12
  */
12
13
  import { resolve } from "node:path";
13
14
  import { checkBudget, countTokensInFileExact, DEFAULT_BUDGET_TOKENS, formatBudgetExceededMessage, } from "../../codebase-to-spec/budget.js";
14
- import { clearCache, ensureCacheDir } from "../../codebase-to-spec/cache.js";
15
+ import { clearCache, ensureCacheDir, writeRunConfig, } from "../../codebase-to-spec/cache.js";
15
16
  import { ExitCode } from "../../codebase-to-spec/exit-codes.js";
16
17
  import { runPack } from "../../codebase-to-spec/pack.js";
17
18
  import { StdoutProgress } from "../../codebase-to-spec/progress.js";
19
+ import { checkForNewerVersion } from "../../codebase-to-spec/version-check.js";
18
20
  import { findProjectRoot } from "../../utils/project-settings.js";
19
21
  export async function packCommand(rawOptions = {}) {
20
22
  const progress = rawOptions.progress ?? new StdoutProgress();
21
23
  const projectRoot = rawOptions.projectRoot ?? findProjectRoot(process.cwd()) ?? process.cwd();
22
24
  const budgetTokens = rawOptions.budgetTokens ?? DEFAULT_BUDGET_TOKENS;
25
+ // Fail-soft staleness check (CTS-PACK-4), started now so the registry
26
+ // round-trip overlaps the pack work and adds no wall-clock on the happy
27
+ // path. Resolves to undefined on any failure or timeout.
28
+ const versionCheck = (rawOptions.checkVersion ?? checkForNewerVersion)();
23
29
  if (rawOptions.fresh) {
24
30
  progress.emit({
25
31
  stage: "pack",
@@ -29,6 +35,13 @@ export async function packCommand(rawOptions = {}) {
29
35
  clearCache(projectRoot);
30
36
  }
31
37
  const paths = ensureCacheDir(projectRoot);
38
+ // Record the isolation decision once, here, so later stages (present) honor
39
+ // it without the caller — or the orchestrating agent — having to carry it
40
+ // across the run. Written on every pack so a re-pack without the flag clears
41
+ // a stale isolated marker. See CTS-PRESENT-5.2.
42
+ writeRunConfig(projectRoot, {
43
+ isolated: rawOptions.ignoreRequirements === true,
44
+ });
32
45
  const scope = rawOptions.scope
33
46
  ? resolve(projectRoot, rawOptions.scope)
34
47
  : undefined;
@@ -54,6 +67,17 @@ export async function packCommand(rawOptions = {}) {
54
67
  message: `Packed ${packResult.fileCount} files → ${paths.overview} (compressed), ${paths.source} (uncompressed)`,
55
68
  data: { fileCount: packResult.fileCount },
56
69
  });
70
+ // Surface a stale install (CTS-PACK-4). Informational only: outputs and
71
+ // exit code are unaffected, and a failed check emits nothing.
72
+ const versionInfo = await versionCheck.catch(() => undefined);
73
+ if (versionInfo?.stale) {
74
+ progress.emit({
75
+ stage: "pack",
76
+ step: "stale-version",
77
+ message: `A newer dotrequirements CLI is available: ${versionInfo.latest} (running ${versionInfo.current}). Re-run skill-install to refresh.`,
78
+ data: { current: versionInfo.current, latest: versionInfo.latest },
79
+ });
80
+ }
57
81
  // Budget check (CTS-CLI-3)
58
82
  const measured = countTokensInFileExact(paths.overview);
59
83
  const budget = checkBudget(measured, budgetTokens);
@@ -8,13 +8,15 @@
8
8
  * rather than specFinal.
9
9
  *
10
10
  * Requirements covered:
11
- * - CTS-PRESENT-1..4 (reuses the legacy present logic)
11
+ * - CTS-PRESENT-1..3 (shared present logic in present.ts)
12
12
  * - CTSO-INTEG-1: orchestrator uses existing deterministic stages unchanged
13
13
  */
14
14
  export interface PresentOrchestratorOptions {
15
15
  forceMode?: "interactive" | "non-interactive";
16
16
  overwrite?: boolean;
17
17
  skipExisting?: boolean;
18
+ /** Override the project root, mainly for tests. */
19
+ projectRoot?: string;
18
20
  }
19
21
  export declare function presentOrchestratorCommand(options?: PresentOrchestratorOptions): Promise<void>;
20
22
  //# sourceMappingURL=present-orchestrator.d.ts.map
@@ -8,11 +8,11 @@
8
8
  * rather than specFinal.
9
9
  *
10
10
  * Requirements covered:
11
- * - CTS-PRESENT-1..4 (reuses the legacy present logic)
11
+ * - CTS-PRESENT-1..3 (shared present logic in present.ts)
12
12
  * - CTSO-INTEG-1: orchestrator uses existing deterministic stages unchanged
13
13
  */
14
14
  import { existsSync, readFileSync } from "node:fs";
15
- import { cachePaths } from "../../codebase-to-spec/cache.js";
15
+ import { cachePaths, readRunConfig } from "../../codebase-to-spec/cache.js";
16
16
  import { ExitCode } from "../../codebase-to-spec/exit-codes.js";
17
17
  import { detectMode } from "../../codebase-to-spec/interactive.js";
18
18
  import { runPresent, } from "../../codebase-to-spec/present.js";
@@ -31,7 +31,7 @@ function resolvePolicy(isInteractive, options) {
31
31
  return "fail-fast";
32
32
  }
33
33
  export async function presentOrchestratorCommand(options = {}) {
34
- const projectRoot = findProjectRoot(process.cwd()) ?? process.cwd();
34
+ const projectRoot = options.projectRoot ?? findProjectRoot(process.cwd()) ?? process.cwd();
35
35
  const paths = cachePaths(projectRoot);
36
36
  if (!existsSync(paths.outline)) {
37
37
  process.stderr.write(`No outline.yaml found at ${paths.outline}. Run the orchestrator's planner first.\n`);
@@ -60,11 +60,17 @@ export async function presentOrchestratorCommand(options = {}) {
60
60
  return;
61
61
  }
62
62
  const legacyOutline = conversationalOutlineToLegacy(outline);
63
+ // Honor the isolation decision recorded at pack time (CTS-PRESENT-5.2). When
64
+ // the run is isolated, write under `.requirements/cts/` so the existing
65
+ // requirements directory is never overwritten — the choice rides the cache,
66
+ // not the agent's memory, so it holds even across a resumed or compacted run.
67
+ const { isolated } = readRunConfig(projectRoot);
63
68
  const result = await runPresent({
64
69
  outline: legacyOutline,
65
70
  finalSpecPath: paths.composedSpec,
66
71
  projectRoot,
67
72
  overwritePolicy: policy,
73
+ outputSubdir: isolated ? "cts" : undefined,
68
74
  });
69
75
  if (!result.allWritten) {
70
76
  if (result.conflictPath) {
@@ -76,6 +82,7 @@ export async function presentOrchestratorCommand(options = {}) {
76
82
  for (const action of result.actions) {
77
83
  process.stdout.write(` ${action.action}: ${action.path}\n`);
78
84
  }
79
- process.stdout.write(`Wrote ${result.actions.filter((a) => a.action === "created" || a.action === "overwrote").length} file(s) to .requirements/\n`);
85
+ const outputDirLabel = isolated ? ".requirements/cts/" : ".requirements/";
86
+ process.stdout.write(`Wrote ${result.actions.filter((a) => a.action === "created" || a.action === "overwrote").length} file(s) to ${outputDirLabel}\n`);
80
87
  }
81
88
  //# sourceMappingURL=present-orchestrator.js.map
@@ -353,8 +353,13 @@ async function inviteFlow(cwd, token) {
353
353
  console.log(`✓ Joined team "${inviteInfo.teamName}"`);
354
354
  }
355
355
  catch (err) {
356
+ // Production redacts ConvexError messages to "Server Error" but keeps
357
+ // error.data — check both, or this graceful path only works in dev
358
+ const data = err.data;
359
+ const dataMessage = typeof data?.message === "string" ? data.message : "";
356
360
  const errorMessage = err instanceof Error ? err.message : String(err);
357
- if (errorMessage.includes("already a member")) {
361
+ if (dataMessage.includes("already a member") ||
362
+ errorMessage.includes("already a member")) {
358
363
  console.log(`Already a member of "${inviteInfo.teamName}"`);
359
364
  }
360
365
  else {
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Pure decision logic for `dotrequirements link`'s non-interactive mode
3
+ * (LINK-10/11/12). No I/O: the link command gathers state (teams, projects)
4
+ * and these functions decide what to do — use a project, create one, rename,
5
+ * or stop with a decision-needed result that carries everything an AI
6
+ * assistant needs to ask the user and retry with a deviation flag.
7
+ */
8
+ export interface TeamOption {
9
+ teamId: string;
10
+ name: string;
11
+ tier: string;
12
+ tierName: string;
13
+ projectCount: number;
14
+ projectLimit: number;
15
+ }
16
+ export interface ProjectOption {
17
+ projectId: string;
18
+ projectName: string;
19
+ projectSlug: string | null;
20
+ isDefault: boolean;
21
+ hasUserContent: boolean;
22
+ }
23
+ export interface LinkFlags {
24
+ yes?: boolean;
25
+ team?: string;
26
+ connect?: string;
27
+ create?: boolean;
28
+ name?: string;
29
+ json?: boolean;
30
+ }
31
+ /**
32
+ * LINK-12.3: any deviation flag implies non-interactive mode.
33
+ */
34
+ export declare function isNonInteractive(flags: LinkFlags): boolean;
35
+ export declare function hasCapacity(team: TeamOption): boolean;
36
+ /** A choice the user must make, with everything needed to ask and retry. */
37
+ export interface DecisionNeeded {
38
+ kind: "decision";
39
+ decision: "team" | "project" | "capacity";
40
+ message: string;
41
+ options: Array<{
42
+ label: string;
43
+ /** Exact flag to pass on retry (LINK-11.4), when retryable via link */
44
+ retryFlag?: string;
45
+ }>;
46
+ }
47
+ export interface NoMatch {
48
+ kind: "no-match";
49
+ flag: "team" | "connect";
50
+ provided: string;
51
+ alternatives: string[];
52
+ }
53
+ export type TeamResolution = {
54
+ kind: "use";
55
+ team: TeamOption;
56
+ } | DecisionNeeded | NoMatch;
57
+ /**
58
+ * LINK-10.1, LINK-11.0, LINK-12.0, LINK-12.4
59
+ */
60
+ export declare function resolveTeam(teams: TeamOption[], flags: LinkFlags): TeamResolution;
61
+ export type ProjectResolution = {
62
+ kind: "connect";
63
+ project: ProjectOption;
64
+ rename: boolean;
65
+ } | {
66
+ kind: "create";
67
+ } | DecisionNeeded | NoMatch;
68
+ /**
69
+ * LINK-10.2/10.3 (default-project rule), LINK-11.1/11.2 (decision menus),
70
+ * LINK-12.1/12.2/12.4 (deviation flags).
71
+ */
72
+ export declare function resolveProject(projects: ProjectOption[], team: TeamOption, flags: LinkFlags): ProjectResolution;
73
+ /**
74
+ * Prompt-free project naming for non-interactive mode (LINK-9.0): explicit
75
+ * flag, then package metadata, then directory name, then a fixed fallback —
76
+ * never a prompt.
77
+ */
78
+ export declare function resolveProjectName(flags: LinkFlags, packageName: string | undefined, directoryName: string | undefined): string;
79
+ //# sourceMappingURL=link-resolution.d.ts.map
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Pure decision logic for `dotrequirements link`'s non-interactive mode
3
+ * (LINK-10/11/12). No I/O: the link command gathers state (teams, projects)
4
+ * and these functions decide what to do — use a project, create one, rename,
5
+ * or stop with a decision-needed result that carries everything an AI
6
+ * assistant needs to ask the user and retry with a deviation flag.
7
+ */
8
+ /**
9
+ * LINK-12.3: any deviation flag implies non-interactive mode.
10
+ */
11
+ export function isNonInteractive(flags) {
12
+ return Boolean(flags.yes || flags.team || flags.connect || flags.create);
13
+ }
14
+ export function hasCapacity(team) {
15
+ return team.projectLimit === -1 || team.projectCount < team.projectLimit;
16
+ }
17
+ /**
18
+ * LINK-10.1, LINK-11.0, LINK-12.0, LINK-12.4
19
+ */
20
+ export function resolveTeam(teams, flags) {
21
+ if (flags.team) {
22
+ const wanted = flags.team.toLowerCase();
23
+ const match = teams.find((t) => t.teamId === flags.team || t.name.toLowerCase() === wanted);
24
+ if (match)
25
+ return { kind: "use", team: match };
26
+ return {
27
+ kind: "no-match",
28
+ flag: "team",
29
+ provided: flags.team,
30
+ alternatives: teams.map((t) => `${t.name} (${t.teamId})`),
31
+ };
32
+ }
33
+ if (teams.length === 1) {
34
+ return { kind: "use", team: teams[0] };
35
+ }
36
+ return {
37
+ kind: "decision",
38
+ decision: "team",
39
+ message: "You belong to multiple teams — which should this project join?",
40
+ options: teams.map((t) => ({
41
+ label: `${t.name} (${t.tierName}, ${formatCapacity(t)})`,
42
+ retryFlag: `--team ${t.teamId}`,
43
+ })),
44
+ };
45
+ }
46
+ /**
47
+ * LINK-10.2/10.3 (default-project rule), LINK-11.1/11.2 (decision menus),
48
+ * LINK-12.1/12.2/12.4 (deviation flags).
49
+ */
50
+ export function resolveProject(projects, team, flags) {
51
+ if (flags.connect) {
52
+ const match = projects.find((p) => p.projectSlug === flags.connect);
53
+ if (match)
54
+ return { kind: "connect", project: match, rename: false };
55
+ return {
56
+ kind: "no-match",
57
+ flag: "connect",
58
+ provided: flags.connect,
59
+ alternatives: projects
60
+ .filter((p) => p.projectSlug)
61
+ .map((p) => `${p.projectName} (${p.projectSlug})`),
62
+ };
63
+ }
64
+ if (flags.create) {
65
+ if (hasCapacity(team))
66
+ return { kind: "create" };
67
+ return atCapacityDecision(projects, team);
68
+ }
69
+ if (projects.length === 0) {
70
+ // A decision with a single possible answer is not a decision: create.
71
+ if (hasCapacity(team))
72
+ return { kind: "create" };
73
+ return atCapacityDecision(projects, team);
74
+ }
75
+ if (projects.length === 1 && projects[0].isDefault) {
76
+ // LINK-10.2: rename the untouched default project to match this repo;
77
+ // LINK-10.3: if the user has put content in it, connect without renaming
78
+ return {
79
+ kind: "connect",
80
+ project: projects[0],
81
+ rename: !projects[0].hasUserContent,
82
+ };
83
+ }
84
+ // LINK-11.1: existing projects the user chose to create — their call
85
+ return {
86
+ kind: "decision",
87
+ decision: "project",
88
+ message: "This team already has projects — connect to one, or create a new one?",
89
+ options: [
90
+ ...projects.map((p) => ({
91
+ label: `${p.projectName}${p.hasUserContent ? "" : " (empty)"}`,
92
+ retryFlag: p.projectSlug ? `--connect ${p.projectSlug}` : undefined,
93
+ })),
94
+ ...(hasCapacity(team)
95
+ ? [{ label: "Create a new project", retryFlag: "--create" }]
96
+ : []),
97
+ ],
98
+ };
99
+ }
100
+ /**
101
+ * LINK-11.2: creating is required (or requested) but the team is at its
102
+ * project limit. None of the remedies is a plain link retry flag.
103
+ */
104
+ function atCapacityDecision(projects, team) {
105
+ return {
106
+ kind: "decision",
107
+ decision: "capacity",
108
+ message: `Team "${team.name}" is at its ${team.tierName} project limit (${formatCapacity(team)}).`,
109
+ options: [
110
+ ...projects
111
+ .filter((p) => p.projectSlug)
112
+ .map((p) => ({
113
+ label: `Connect to existing project ${p.projectName}`,
114
+ retryFlag: `--connect ${p.projectSlug}`,
115
+ })),
116
+ { label: "Delete a project you no longer need (in the web app)" },
117
+ { label: "Upgrade the team for more projects" },
118
+ ],
119
+ };
120
+ }
121
+ function formatCapacity(team) {
122
+ return team.projectLimit === -1
123
+ ? `${team.projectCount}/unlimited projects`
124
+ : `${team.projectCount}/${team.projectLimit} projects`;
125
+ }
126
+ /**
127
+ * Prompt-free project naming for non-interactive mode (LINK-9.0): explicit
128
+ * flag, then package metadata, then directory name, then a fixed fallback —
129
+ * never a prompt.
130
+ */
131
+ export function resolveProjectName(flags, packageName, directoryName) {
132
+ if (flags.name)
133
+ return flags.name;
134
+ if (packageName)
135
+ return packageName;
136
+ if (directoryName && directoryName !== "/" && directoryName !== ".") {
137
+ return directoryName;
138
+ }
139
+ return "my-project";
140
+ }
141
+ //# sourceMappingURL=link-resolution.js.map
@@ -1,14 +1,24 @@
1
+ import { type LinkFlags } from "./link-resolution.js";
2
+ export interface LinkCommandOptions extends LinkFlags {
3
+ }
1
4
  /**
2
- * Link command - connect or reconnect local project to cloud.
5
+ * Link command - connect or reconnect local project to cloud, creating the
6
+ * cloud project when needed. Link owns the "upgrade local to cloud"
7
+ * transition.
3
8
  *
4
9
  * Requirements:
5
10
  * - LINK-1: Requires existing .requirements/ folder
6
11
  * - LINK-2: Offers refresh when credentials exist
7
12
  * - LINK-3: Uses inline OAuth (no persistent tokens)
8
- * - LINK-4, LINK-5: Team and project selection
13
+ * - LINK-4, LINK-5: Team and project selection (including the create option)
9
14
  * - LINK-6: Saves credentials to project-settings.json
10
- * - LINK-7: Optionally pulls requirements
15
+ * - LINK-7: Optionally pulls requirements (interactive only)
11
16
  * - LINK-8: Handles network errors gracefully
17
+ * - LINK-9: Creates the cloud project when chosen
18
+ * - LINK-10/11/12: Non-interactive mode for AI-driven setup
19
+ * - LINK-13: Non-interactive success includes share artifacts (team-invite
20
+ * URL + read-only pull command) so one "yes" puts the spec in front of
21
+ * the team
12
22
  */
13
- export declare function linkCommand(): Promise<void>;
23
+ export declare function linkCommand(options?: LinkCommandOptions): Promise<void>;
14
24
  //# sourceMappingURL=link.d.ts.map