@popoverai/dotrequirements 0.27.4 → 0.28.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 (50) hide show
  1. package/README.md +24 -20
  2. package/dist/cli.js +34 -12
  3. package/dist/commands/aliases.d.ts +26 -0
  4. package/dist/commands/aliases.js +31 -0
  5. package/dist/commands/diff.d.ts +14 -0
  6. package/dist/commands/diff.js +62 -0
  7. package/dist/commands/init.js +5 -5
  8. package/dist/commands/link.d.ts +1 -1
  9. package/dist/commands/link.js +10 -7
  10. package/dist/commands/sync-common.d.ts +21 -0
  11. package/dist/commands/sync-common.js +24 -0
  12. package/dist/commands/sync.d.ts +26 -0
  13. package/dist/commands/sync.js +182 -0
  14. package/dist/convex.d.ts +1 -1
  15. package/dist/convex.js +2 -2
  16. package/dist/push/core.d.ts +18 -116
  17. package/dist/push/core.js +16 -267
  18. package/dist/push/index.d.ts +3 -3
  19. package/dist/push/index.js +4 -4
  20. package/dist/requirements/style-guide.js +3 -3
  21. package/dist/schema/schemas.js +1 -1
  22. package/dist/sync/compare.d.ts +21 -0
  23. package/dist/sync/compare.js +285 -0
  24. package/dist/sync/execute.d.ts +48 -0
  25. package/dist/sync/execute.js +186 -0
  26. package/dist/sync/index.d.ts +21 -0
  27. package/dist/sync/index.js +52 -0
  28. package/dist/sync/local-files.d.ts +26 -0
  29. package/dist/sync/local-files.js +91 -0
  30. package/dist/sync/plan.d.ts +39 -0
  31. package/dist/sync/plan.js +90 -0
  32. package/dist/sync/render.d.ts +17 -0
  33. package/dist/sync/render.js +46 -0
  34. package/dist/sync/segment.d.ts +40 -0
  35. package/dist/sync/segment.js +76 -0
  36. package/dist/sync/snapshot.d.ts +30 -0
  37. package/dist/sync/snapshot.js +118 -0
  38. package/dist/sync/types.d.ts +82 -0
  39. package/dist/sync/types.js +12 -0
  40. package/dist/templates/context-file-section.md +2 -1
  41. package/dist/templates/requirements-readme.js +3 -4
  42. package/dist/templates/requirements-readme.ts +3 -4
  43. package/dist/templates/skills/codebase-to-spec/SKILL.md +2 -2
  44. package/dist/utils/project-settings.d.ts +8 -2
  45. package/dist/utils/project-settings.js +47 -24
  46. package/package.json +1 -1
  47. package/dist/commands/pull.d.ts +0 -8
  48. package/dist/commands/pull.js +0 -230
  49. package/dist/commands/push.d.ts +0 -6
  50. package/dist/commands/push.js +0 -244
package/README.md CHANGED
@@ -124,37 +124,41 @@ Link your local environment to an existing project (when you already have a proj
124
124
  dotreq link
125
125
  ```
126
126
 
127
- ### `dotreq pull`
127
+ ### `dotreq diff`
128
128
 
129
- Sync requirements from dot•requirements cloud to local `.requirements/` files.
129
+ Show how the repo and the cloud differ, read-only. Each document gets a verdict (in sync, additions in repo, additions in cloud, conflict, only in repo, only in cloud, invalid file).
130
130
 
131
131
  ```bash
132
- dotreq pull
133
- dotreq pull --project <project-id>
134
- dotreq pull --document <document-id>
135
- dotreq pull --share <token>
132
+ dotreq diff
133
+ dotreq diff auth.requirements.md # detail for one or more documents
134
+ dotreq diff --exit-code # exit 1 on any drift (for CI)
136
135
  ```
137
136
 
138
- **First-time setup with a share token:**
137
+ ### `dotreq sync`
139
138
 
140
- If a team member shares a pull command with you, you can pull requirements without creating an account:
139
+ Reconcile the repo and the cloud. With no flags, both sides contribute their additions and nothing contested is touched.
141
140
 
142
141
  ```bash
143
- npx @popoverai/dotrequirements@latest pull --share drt_abc123...
142
+ dotreq sync # both sides contribute (additive, safe)
143
+ dotreq sync --cloud-contributes # bring the cloud's additions down only
144
+ dotreq sync --repo-contributes # send the repo's additions up only
145
+ dotreq sync --repo-wins # repo is authoritative (may delete in cloud)
146
+ dotreq sync --cloud-wins # cloud is authoritative (may delete locally)
147
+ dotreq sync --yes # skip the deletion confirmation
144
148
  ```
145
149
 
146
- This gives you read-only access to view requirements. To push changes or report coverage, run `dotreq link` afterward.
150
+ Additive modes never delete; a `--wins` mode can, and lists every document it will permanently delete before writing.
147
151
 
148
- ### `dotreq push`
152
+ **First-time setup with a share token:**
149
153
 
150
- Push local requirements to dot•requirements cloud.
154
+ If a team member shares a sync command with you, you can sync requirements down without creating an account:
151
155
 
152
156
  ```bash
153
- dotreq push
154
- dotreq push .requirements/auth.requirements.md
155
- dotreq push --yes # Skip confirmation
157
+ npx @popoverai/dotrequirements@latest sync --share drt_abc123...
156
158
  ```
157
159
 
160
+ This gives you read-only access to view requirements. To contribute changes or report coverage, run `dotreq link` afterward.
161
+
158
162
  ### `dotreq validate`
159
163
 
160
164
  Validate requirements files against the schema.
@@ -608,24 +612,24 @@ See [MARKDOWN_SCHEMA.md](https://github.com/PopoverAI/dotrequirements/blob/main/
608
612
 
609
613
  ## AI Assistant Integration
610
614
 
611
- AI coding assistants use the CLI verbs directly — no MCP server to install or configure. `dotreq ai-setup` writes the requirements-driven workflow guidance into your assistant's context file (CLAUDE.md / AGENTS.md), which tells the agent when to explore, validate, style-review, and push. For chat apps (Claude, ChatGPT), use the dot•requirements remote connector instead.
615
+ AI coding assistants use the CLI verbs directly — no MCP server to install or configure. `dotreq ai-setup` writes the requirements-driven workflow guidance into your assistant's context file (CLAUDE.md / AGENTS.md), which tells the agent when to explore, validate, style-review, and sync. For chat apps (Claude, ChatGPT), use the dot•requirements remote connector instead.
612
616
 
613
617
  > The local MCP server shipped by earlier versions is retired. `dotreq ai-setup` removes stale MCP registrations; `dotreq mcp` now exits with a pointer to this migration.
614
618
 
615
619
  ### CI/CD Mode
616
620
 
617
- For CI/CD environments (GitHub Actions, etc.), use the global `--auth-from-env` flag so cloud commands read credentials from environment variables instead of `project-settings.json`:
621
+ In CI/CD environments (GitHub Actions, etc.) where no `.requirements/project-settings.json` is present, the CLI authenticates from environment variables automatically — no flag required — and prints a notice to stderr:
618
622
 
619
623
  ```bash
620
- dotreq --auth-from-env push
621
- dotreq --auth-from-env style-check .requirements/auth.requirements.md --source cloud
624
+ dotreq sync --repo-contributes
625
+ dotreq style-check .requirements/auth.requirements.md --source cloud
622
626
  ```
623
627
 
624
628
  Required environment variables:
625
629
  - `DOTREQ_PROJECT_ID` — Your project slug
626
630
  - `DOTREQ_PROJECT_SECRET` — Your project secret
627
631
 
628
- **Important:** Without `--auth-from-env`, these environment variables are ignored. This explicit opt-in prevents credential conflicts between local and CI environments.
632
+ **Important:** A settings file on disk always wins over these environment variables, so a local checkout is never overridden by an ambient environment. The environment is used only when no settings file is found.
629
633
 
630
634
  See the [CI/CD Integration docs](https://docs.dotrequirements.io/tools/ai/ci-cd) for complete examples.
631
635
 
package/dist/cli.js CHANGED
@@ -5,8 +5,10 @@ import { fileURLToPath } from "node:url";
5
5
  import { Command } from "commander";
6
6
  import { acceptanceTestCommand } from "./commands/acceptance-test.js";
7
7
  import { aiSetupCommand } from "./commands/ai-setup.js";
8
+ import { pullAlias, pushAlias } from "./commands/aliases.js";
8
9
  import { registerCodebaseToSpec } from "./commands/codebase-to-spec/index.js";
9
10
  import { createRequirementDocumentCommand } from "./commands/create-requirement-document.js";
11
+ import { diffCommand } from "./commands/diff.js";
10
12
  import { finalizeCommand } from "./commands/finalize.js";
11
13
  import { getCommand } from "./commands/get.js";
12
14
  import { initCommand } from "./commands/init.js";
@@ -14,17 +16,16 @@ import { linkCommand } from "./commands/link.js";
14
16
  import { listCommand } from "./commands/list.js";
15
17
  import { mcpCommand } from "./commands/mcp.js";
16
18
  import { prepareCommand } from "./commands/prepare.js";
17
- import { pullCommand } from "./commands/pull.js";
18
- import { pushCommand } from "./commands/push.js";
19
19
  import { reportCommand } from "./commands/report.js";
20
20
  import { requirementsForCommand } from "./commands/requirements-for.js";
21
21
  import { reviewTestCommand } from "./commands/review-test.js";
22
22
  import { searchCommand } from "./commands/search.js";
23
23
  import { styleCheckCommand } from "./commands/style-check.js";
24
+ import { syncCliAction } from "./commands/sync.js";
24
25
  import { testsForCommand } from "./commands/tests-for.js";
25
26
  import { validateCommand } from "./commands/validate.js";
26
27
  import { loadEnvFile } from "./utils/env.js";
27
- import { setAuthFromEnv } from "./utils/project-settings.js";
28
+ import { setPreferEnvCredentials } from "./utils/project-settings.js";
28
29
  // Read version from package.json
29
30
  const __filename = fileURLToPath(import.meta.url);
30
31
  const __dirname = dirname(__filename);
@@ -57,13 +58,14 @@ program
57
58
  .name("dotrequirements")
58
59
  .description("Requirements tracking CLI with test harness")
59
60
  .version(VERSION)
60
- // AUTHZ-6: explicit CI/CD credential injection — with this flag, cloud
61
- // commands read DOTREQ_PROJECT_ID / DOTREQ_PROJECT_SECRET instead of
62
- // project-settings.json; without it those variables are ignored.
63
- .option("--auth-from-env", "Read cloud credentials from DOTREQ_PROJECT_ID and DOTREQ_PROJECT_SECRET (CI/CD)")
61
+ // SYNC-ALIAS-1.3: deprecated — environment fallback is automatic now (AUTHZ-6),
62
+ // but invocations already in users' CI pass this flag; keep its original
63
+ // semantics (env replaces file discovery) rather than hard-erroring.
64
+ .option("--auth-from-env", "Deprecated: environment credentials are now used automatically when no settings file is present")
64
65
  .hook("preAction", (thisCommand) => {
65
66
  if (thisCommand.opts().authFromEnv) {
66
- setAuthFromEnv(true);
67
+ process.stderr.write("Note: --auth-from-env is deprecated — the CLI now authenticates from DOTREQ_PROJECT_ID / DOTREQ_PROJECT_SECRET automatically when no settings file is present.\n");
68
+ setPreferEnvCredentials(true);
67
69
  }
68
70
  });
69
71
  program
@@ -82,18 +84,38 @@ program
82
84
  .option("--create", "Create a new project (implies --yes)")
83
85
  .option("-n, --name <name>", "Project name for --create or rename")
84
86
  .action(wrapCommand(linkCommand));
87
+ program
88
+ .command("diff [scope...]")
89
+ .description("Show how the repo and the cloud differ (read-only)")
90
+ .option("-s, --share <token>", "Read-only share token (no setup required)")
91
+ .option("-p, --project <id>", "Override the project slug")
92
+ .option("--exit-code", "Exit 1 when any document is not in sync (for CI)")
93
+ .action(wrapCommand(diffCommand));
94
+ program
95
+ .command("sync [scope...]")
96
+ .description("Reconcile the repo and the cloud")
97
+ .option("--cloud-contributes", "Apply only the cloud's additions locally")
98
+ .option("--repo-contributes", "Apply only the repo's additions to the cloud")
99
+ .option("--cloud-wins", "Cloud is authority: the repo matches it (may delete)")
100
+ .option("--repo-wins", "Repo is authority: the cloud matches it (may delete)")
101
+ .option("-s, --share <token>", "Read-only share token (no setup required)")
102
+ .option("-p, --project <id>", "Override the project slug")
103
+ .option("-y, --yes", "Skip the deletion confirmation prompt")
104
+ .action(wrapCommand(syncCliAction));
105
+ // Deprecated aliases into `sync`, kept for commands already in the wild
106
+ // (SYNC-ALIAS-1). Each prints a deprecation notice naming its new spelling.
85
107
  program
86
108
  .command("pull")
87
- .description("Sync requirements from cloud to local .requirements/ files")
109
+ .description("Deprecated: use `sync --cloud-contributes`")
88
110
  .option("-p, --project <id>", "Project ID to sync")
89
111
  .option("-d, --document <id>", "Specific document ID to sync")
90
112
  .option("-s, --share <token>", "Read-only share token for quick onboarding (no setup required)")
91
- .action(wrapCommand(pullCommand));
113
+ .action(wrapCommand(pullAlias));
92
114
  program
93
115
  .command("push [file]")
94
- .description("Push local requirements from .requirements/ to cloud")
116
+ .description("Deprecated: use `sync --repo-contributes`")
95
117
  .option("-y, --yes", "Skip confirmation prompt")
96
- .action(wrapCommand(pushCommand));
118
+ .action(wrapCommand(pushAlias));
97
119
  program
98
120
  .command("validate")
99
121
  .description("Validate requirements files in .requirements/")
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Deprecated `push` / `pull` verbs, kept as aliases into `dotreq sync` for the
3
+ * commands already in the wild — distributed `pull --share` onboarding
4
+ * commands, users' CI pipelines, ai-setup-installed guidance (SYNC-ALIAS-1).
5
+ * Each prints a deprecation notice naming its new spelling. First-party code
6
+ * calls `sync` directly; only these aliases remain.
7
+ */
8
+ interface PullAliasOptions {
9
+ project?: string;
10
+ document?: string;
11
+ share?: string;
12
+ }
13
+ /** `dotreq pull` → `dotreq sync --cloud-contributes` (SYNC-ALIAS-1.0). */
14
+ export declare function pullAlias(options: PullAliasOptions): Promise<void>;
15
+ interface PushAliasOptions {
16
+ yes?: boolean;
17
+ }
18
+ /** `dotreq push [file]` → `dotreq sync --repo-contributes [scope]` (SYNC-ALIAS-1.1).
19
+ *
20
+ * Deliberately safer than the push it shadows: old push overwrote a diverged
21
+ * cloud document after a y/N (or silently with --yes); the alias flags it as a
22
+ * conflict instead and exits non-zero. The notice teaches both halves of the
23
+ * migration, since --repo-contributes alone doesn't reproduce the overwrite. */
24
+ export declare function pushAlias(file: string | undefined, options: PushAliasOptions): Promise<void>;
25
+ export {};
26
+ //# sourceMappingURL=aliases.d.ts.map
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Deprecated `push` / `pull` verbs, kept as aliases into `dotreq sync` for the
3
+ * commands already in the wild — distributed `pull --share` onboarding
4
+ * commands, users' CI pipelines, ai-setup-installed guidance (SYNC-ALIAS-1).
5
+ * Each prints a deprecation notice naming its new spelling. First-party code
6
+ * calls `sync` directly; only these aliases remain.
7
+ */
8
+ import { syncCliAction } from "./sync.js";
9
+ /** `dotreq pull` → `dotreq sync --cloud-contributes` (SYNC-ALIAS-1.0). */
10
+ export async function pullAlias(options) {
11
+ console.warn("Note: `dotreq pull` is deprecated. Use `dotreq sync --cloud-contributes`.");
12
+ const scope = options.document ? [options.document] : [];
13
+ await syncCliAction(scope, {
14
+ cloudContributes: true,
15
+ share: options.share,
16
+ project: options.project,
17
+ });
18
+ }
19
+ /** `dotreq push [file]` → `dotreq sync --repo-contributes [scope]` (SYNC-ALIAS-1.1).
20
+ *
21
+ * Deliberately safer than the push it shadows: old push overwrote a diverged
22
+ * cloud document after a y/N (or silently with --yes); the alias flags it as a
23
+ * conflict instead and exits non-zero. The notice teaches both halves of the
24
+ * migration, since --repo-contributes alone doesn't reproduce the overwrite. */
25
+ export async function pushAlias(file, options) {
26
+ console.warn("Note: `dotreq push` is deprecated. Use `dotreq sync --repo-contributes` — " +
27
+ "or `dotreq sync <file> --repo-wins` where push would have overwritten a cloud-side change.");
28
+ const scope = file ? [file] : [];
29
+ await syncCliAction(scope, { repoContributes: true, yes: options.yes });
30
+ }
31
+ //# sourceMappingURL=aliases.js.map
@@ -0,0 +1,14 @@
1
+ /**
2
+ * `dotreq diff` — read-only comparison of the repo and the cloud (DIFF-*).
3
+ *
4
+ * With no scope it lists a verdict per document; scoped to documents it unfolds
5
+ * the detail one level. Exits 0 regardless of verdicts unless `--exit-code` is
6
+ * passed, which makes any non-in-sync document exit 1 (the CI drift gate).
7
+ */
8
+ import { type SyncAuthOptions } from "./sync-common.js";
9
+ interface DiffOptions extends SyncAuthOptions {
10
+ exitCode?: boolean;
11
+ }
12
+ export declare function diffCommand(scope: string[], options: DiffOptions): Promise<void>;
13
+ export {};
14
+ //# sourceMappingURL=diff.d.ts.map
@@ -0,0 +1,62 @@
1
+ /**
2
+ * `dotreq diff` — read-only comparison of the repo and the cloud (DIFF-*).
3
+ *
4
+ * With no scope it lists a verdict per document; scoped to documents it unfolds
5
+ * the detail one level. Exits 0 regardless of verdicts unless `--exit-code` is
6
+ * passed, which makes any non-in-sync document exit 1 (the CI drift gate).
7
+ */
8
+ import { acquireCloudSnapshot, acquireLocalSnapshot, compareSnapshots, filterByScope, formatUnitDetail, formatVerdictLine, resolutionHint, } from "../sync/index.js";
9
+ import { brand } from "../utils/brand.js";
10
+ import { resolveCloudAuth } from "./sync-common.js";
11
+ export async function diffCommand(scope, options) {
12
+ const workspaceRoot = process.cwd();
13
+ const auth = resolveCloudAuth(options);
14
+ const [local, cloud] = await Promise.all([
15
+ acquireLocalSnapshot(workspaceRoot),
16
+ acquireCloudSnapshot(auth),
17
+ ]);
18
+ const comparison = compareSnapshots(local, cloud);
19
+ const { documents, unmatched } = filterByScope(comparison, scope, workspaceRoot);
20
+ for (const token of unmatched) {
21
+ console.log(`No document matched scope "${token}".`);
22
+ }
23
+ // A CI gate pointed at a scope that matches nothing must not report green —
24
+ // the gate's premise (these documents exist to compare) is broken.
25
+ if (options.exitCode && unmatched.length > 0) {
26
+ process.exitCode = 1;
27
+ }
28
+ // DIFF-2: any scope argument means document-scope → verbose. No scope means
29
+ // project-scope → verdict list.
30
+ const verbose = scope.length > 0;
31
+ const notInSync = documents.filter((d) => d.verdict !== "in_sync");
32
+ // DIFF-1.2: don't list every document when everything is in sync.
33
+ if (notInSync.length === 0 && documents.length > 0) {
34
+ console.log(`✓ Repo and ${brand} cloud are in sync (${documents.length} document(s)).`);
35
+ return;
36
+ }
37
+ if (documents.length === 0) {
38
+ console.log("No documents to compare.");
39
+ return;
40
+ }
41
+ console.log(`\n=== Repo vs ${brand} cloud ===\n`);
42
+ for (const doc of documents) {
43
+ console.log(formatVerdictLine(doc));
44
+ if (doc.verdict === "invalid_file" && doc.error) {
45
+ console.log(` ${doc.error}`);
46
+ }
47
+ if (verbose && doc.details && doc.details.length > 0) {
48
+ for (const detail of doc.details) {
49
+ console.log(formatUnitDetail(detail));
50
+ }
51
+ }
52
+ if (doc.verdict === "conflict") {
53
+ console.log(` ${resolutionHint(doc)}`);
54
+ }
55
+ }
56
+ console.log();
57
+ // DIFF-6: exit non-zero on drift only when asked (the CI gate).
58
+ if (options.exitCode && notInSync.length > 0) {
59
+ process.exitCode = 1;
60
+ }
61
+ }
62
+ //# sourceMappingURL=diff.js.map
@@ -14,7 +14,7 @@ import { getOrCreateProjectSecret, promptExpiryDays, selectProject, } from "../u
14
14
  import { findProjectRoot, writeProjectSettings, } from "../utils/project-settings.js";
15
15
  import { promptChoice, promptConfirm } from "../utils/prompts.js";
16
16
  import { aiSetupCommand } from "./ai-setup.js";
17
- import { pullCommand } from "./pull.js";
17
+ import { syncCommand } from "./sync.js";
18
18
  export async function initCommand(options = {}) {
19
19
  try {
20
20
  console.log(`Initializing ${brand} project...\n`);
@@ -299,8 +299,8 @@ async function connectExistingProjectFlow(cwd, client, team) {
299
299
  ensureGitignore(cwd);
300
300
  console.log("✓ Updated .gitignore\n");
301
301
  // INIT-6.2: Pull existing requirements from cloud
302
- console.log("Pulling requirements from cloud...");
303
- await pullCommand({ project: projectId });
302
+ console.log("Syncing requirements from cloud...");
303
+ await syncCommand([], { cloudContributes: true, project: projectId });
304
304
  console.log(`\n✓ Successfully connected to project: ${selectedProject.projectName}`);
305
305
  console.log(` Project ID: ${projectId}`);
306
306
  // INIT-6.3: Show guidance
@@ -401,8 +401,8 @@ async function inviteFlow(cwd, token) {
401
401
  ensureGitignore(cwd);
402
402
  console.log("✓ Updated .gitignore\n");
403
403
  // Pull existing requirements from cloud
404
- console.log("Pulling requirements from cloud...");
405
- await pullCommand({ project: projectId });
404
+ console.log("Syncing requirements from cloud...");
405
+ await syncCommand([], { cloudContributes: true, project: projectId });
406
406
  console.log(`\n✓ Successfully connected to project: ${selectedProject.projectName}`);
407
407
  console.log(` Project ID: ${projectId}`);
408
408
  // Show guidance
@@ -17,7 +17,7 @@ export interface LinkCommandOptions extends LinkFlags {
17
17
  * - LINK-9: Creates the cloud project when chosen
18
18
  * - LINK-10/11/12: Non-interactive mode for AI-driven setup
19
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
20
+ * URL + read-only sync command) so one "yes" puts the spec in front of
21
21
  * the team
22
22
  */
23
23
  export declare function linkCommand(options?: LinkCommandOptions): Promise<void>;
@@ -11,7 +11,7 @@ import { getOrCreateProjectSecret, promptExpiryDays, selectTeam, } from "../util
11
11
  import { findProjectRoot, readProjectSettings, writeProjectSettings, } from "../utils/project-settings.js";
12
12
  import { promptChoice, promptConfirm } from "../utils/prompts.js";
13
13
  import { hasCapacity, isNonInteractive, resolveProject, resolveProjectName, resolveTeam, } from "./link-resolution.js";
14
- import { pullCommand } from "./pull.js";
14
+ import { syncCommand } from "./sync.js";
15
15
  /**
16
16
  * Link command - connect or reconnect local project to cloud, creating the
17
17
  * cloud project when needed. Link owns the "upgrade local to cloud"
@@ -28,7 +28,7 @@ import { pullCommand } from "./pull.js";
28
28
  * - LINK-9: Creates the cloud project when chosen
29
29
  * - LINK-10/11/12: Non-interactive mode for AI-driven setup
30
30
  * - LINK-13: Non-interactive success includes share artifacts (team-invite
31
- * URL + read-only pull command) so one "yes" puts the spec in front of
31
+ * URL + read-only sync command) so one "yes" puts the spec in front of
32
32
  * the team
33
33
  */
34
34
  export async function linkCommand(options = {}) {
@@ -68,8 +68,8 @@ async function runNonInteractiveLink(options) {
68
68
  if (outcome.inviteUrl) {
69
69
  console.log(` Invite a teammate (web): ${outcome.inviteUrl}`);
70
70
  }
71
- if (outcome.sharePullCommand) {
72
- console.log(` Share read-only to an IDE: ${outcome.sharePullCommand}`);
71
+ if (outcome.shareSyncCommand) {
72
+ console.log(` Share read-only to an IDE: ${outcome.shareSyncCommand}`);
73
73
  }
74
74
  }
75
75
  if (outcome.status === "decision_needed") {
@@ -265,7 +265,7 @@ async function mintShareArtifacts(client, teamId, projectId) {
265
265
  const share = (await client.mutation(api.projectSecrets.mutations.ensureShareToken,
266
266
  // biome-ignore lint/suspicious/noExplicitAny: CLI workspace doesn't import convex's branded Id type; the runtime value is a plain string the server validates.
267
267
  { target: { type: "project", id: projectId } }));
268
- artifacts.sharePullCommand = `npx -y @popoverai/dotrequirements@latest pull --share ${share.token}`;
268
+ artifacts.shareSyncCommand = `npx -y @popoverai/dotrequirements@latest sync --share ${share.token}`;
269
269
  }
270
270
  catch {
271
271
  // LINK-13.3: connection already succeeded — omit rather than fail
@@ -405,8 +405,11 @@ async function runInteractiveLink(options) {
405
405
  // LINK-7: Optionally pull requirements
406
406
  const shouldPull = await promptConfirm("Pull requirements from cloud now?", true);
407
407
  if (shouldPull) {
408
- console.log("\nPulling requirements...");
409
- await pullCommand({ project: projectSlugOrId });
408
+ console.log("\nSyncing requirements...");
409
+ await syncCommand([], {
410
+ cloudContributes: true,
411
+ project: projectSlugOrId,
412
+ });
410
413
  }
411
414
  // LINK-7.2: Success message
412
415
  console.log(`\n✓ Successfully linked to project: ${projectName}`);
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Shared plumbing for `dotreq diff` and `dotreq sync`: resolving cloud
3
+ * credentials (project settings / environment / share token) into the
4
+ * comparator's CloudAuth.
5
+ */
6
+ import type { CloudAuth } from "../sync/index.js";
7
+ export interface SyncAuthOptions {
8
+ /** Read-only share token: authenticates without project settings. */
9
+ share?: string;
10
+ /** Override the project slug (otherwise from settings/environment). */
11
+ project?: string;
12
+ }
13
+ /**
14
+ * Resolve the cloud credentials for a diff/sync run. A share token authenticates
15
+ * on its own (SHARE-TOKEN-CLI-1.1) and carries no slug — the read-only wrapper
16
+ * resolves the project from the token, and being read-only it can never write
17
+ * back (SHARE-TOKEN-CLI-1.3). Otherwise credentials come from the settings file
18
+ * or the environment (AUTHZ-6).
19
+ */
20
+ export declare function resolveCloudAuth(opts: SyncAuthOptions): CloudAuth;
21
+ //# sourceMappingURL=sync-common.d.ts.map
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Shared plumbing for `dotreq diff` and `dotreq sync`: resolving cloud
3
+ * credentials (project settings / environment / share token) into the
4
+ * comparator's CloudAuth.
5
+ */
6
+ import { getProjectCredentials } from "../utils/project-settings.js";
7
+ /**
8
+ * Resolve the cloud credentials for a diff/sync run. A share token authenticates
9
+ * on its own (SHARE-TOKEN-CLI-1.1) and carries no slug — the read-only wrapper
10
+ * resolves the project from the token, and being read-only it can never write
11
+ * back (SHARE-TOKEN-CLI-1.3). Otherwise credentials come from the settings file
12
+ * or the environment (AUTHZ-6).
13
+ */
14
+ export function resolveCloudAuth(opts) {
15
+ if (opts.share) {
16
+ return { projectSecret: opts.share };
17
+ }
18
+ const creds = getProjectCredentials();
19
+ return {
20
+ projectSlug: opts.project ?? creds.projectId,
21
+ projectSecret: creds.projectSecret,
22
+ };
23
+ }
24
+ //# sourceMappingURL=sync-common.js.map
@@ -0,0 +1,26 @@
1
+ /**
2
+ * `dotreq sync` — reconcile the repo and the cloud under a direction/authority
3
+ * mode (SYNC-MODE-*). The daily gesture is the unmarked form; flags narrow
4
+ * direction (`--cloud-contributes` / `--repo-contributes`) or assign authority
5
+ * (`--repo-wins` / `--cloud-wins`).
6
+ */
7
+ import { type SyncAuthOptions } from "./sync-common.js";
8
+ interface SyncOptions extends SyncAuthOptions {
9
+ cloudContributes?: boolean;
10
+ repoContributes?: boolean;
11
+ cloudWins?: boolean;
12
+ repoWins?: boolean;
13
+ yes?: boolean;
14
+ }
15
+ /** How a sync run ended, for the caller to translate into an exit code. */
16
+ export type SyncRunResult = "clean" | "trouble" | "cancelled";
17
+ /**
18
+ * The CLI entry point: exits non-zero on unresolved trouble (SYNC-MODE-1.5,
19
+ * SYNC-FAIL-1/4). `init`/`link` call `syncCommand` directly instead — their
20
+ * success is linking/onboarding, and a conflict in one spec file must not fail
21
+ * the whole command (the report lines still print).
22
+ */
23
+ export declare function syncCliAction(scope: string[], options: SyncOptions): Promise<void>;
24
+ export declare function syncCommand(scope: string[], options: SyncOptions): Promise<SyncRunResult>;
25
+ export {};
26
+ //# sourceMappingURL=sync.d.ts.map