@popoverai/dotrequirements 0.27.3 → 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.
- package/README.md +24 -20
- package/dist/cli.js +34 -12
- package/dist/commands/aliases.d.ts +26 -0
- package/dist/commands/aliases.js +31 -0
- package/dist/commands/diff.d.ts +14 -0
- package/dist/commands/diff.js +62 -0
- package/dist/commands/init.js +5 -5
- package/dist/commands/link.d.ts +1 -1
- package/dist/commands/link.js +13 -7
- package/dist/commands/sync-common.d.ts +21 -0
- package/dist/commands/sync-common.js +24 -0
- package/dist/commands/sync.d.ts +26 -0
- package/dist/commands/sync.js +182 -0
- package/dist/convex.d.ts +1 -1
- package/dist/convex.js +2 -2
- package/dist/push/core.d.ts +18 -116
- package/dist/push/core.js +16 -267
- package/dist/push/index.d.ts +3 -3
- package/dist/push/index.js +4 -4
- package/dist/requirements/style-guide.js +3 -3
- package/dist/schema/schemas.js +1 -1
- package/dist/sync/compare.d.ts +21 -0
- package/dist/sync/compare.js +285 -0
- package/dist/sync/execute.d.ts +48 -0
- package/dist/sync/execute.js +186 -0
- package/dist/sync/index.d.ts +21 -0
- package/dist/sync/index.js +52 -0
- package/dist/sync/local-files.d.ts +26 -0
- package/dist/sync/local-files.js +91 -0
- package/dist/sync/plan.d.ts +39 -0
- package/dist/sync/plan.js +90 -0
- package/dist/sync/render.d.ts +17 -0
- package/dist/sync/render.js +46 -0
- package/dist/sync/segment.d.ts +40 -0
- package/dist/sync/segment.js +76 -0
- package/dist/sync/snapshot.d.ts +30 -0
- package/dist/sync/snapshot.js +118 -0
- package/dist/sync/types.d.ts +82 -0
- package/dist/sync/types.js +12 -0
- package/dist/templates/context-file-section.md +2 -1
- package/dist/templates/requirements-readme.js +3 -4
- package/dist/templates/requirements-readme.ts +3 -4
- package/dist/templates/skills/codebase-to-spec/SKILL.md +2 -2
- package/dist/utils/oauth-flow.d.ts +2 -0
- package/dist/utils/oauth-flow.js +8 -1
- package/dist/utils/project-settings.d.ts +8 -2
- package/dist/utils/project-settings.js +47 -24
- package/package.json +1 -1
- package/dist/commands/pull.d.ts +0 -8
- package/dist/commands/pull.js +0 -230
- package/dist/commands/push.d.ts +0 -6
- 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
|
|
127
|
+
### `dotreq diff`
|
|
128
128
|
|
|
129
|
-
|
|
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
|
|
133
|
-
dotreq
|
|
134
|
-
dotreq
|
|
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
|
-
|
|
137
|
+
### `dotreq sync`
|
|
139
138
|
|
|
140
|
-
|
|
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
|
-
|
|
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
|
-
|
|
150
|
+
Additive modes never delete; a `--wins` mode can, and lists every document it will permanently delete before writing.
|
|
147
151
|
|
|
148
|
-
|
|
152
|
+
**First-time setup with a share token:**
|
|
149
153
|
|
|
150
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 --
|
|
621
|
-
dotreq
|
|
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:**
|
|
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 {
|
|
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
|
-
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
.option("--auth-from-env", "
|
|
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
|
-
|
|
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("
|
|
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(
|
|
113
|
+
.action(wrapCommand(pullAlias));
|
|
92
114
|
program
|
|
93
115
|
.command("push [file]")
|
|
94
|
-
.description("
|
|
116
|
+
.description("Deprecated: use `sync --repo-contributes`")
|
|
95
117
|
.option("-y, --yes", "Skip confirmation prompt")
|
|
96
|
-
.action(wrapCommand(
|
|
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
|
package/dist/commands/init.js
CHANGED
|
@@ -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 {
|
|
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("
|
|
303
|
-
await
|
|
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("
|
|
405
|
-
await
|
|
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
|
package/dist/commands/link.d.ts
CHANGED
|
@@ -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
|
|
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>;
|
package/dist/commands/link.js
CHANGED
|
@@ -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 {
|
|
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
|
|
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.
|
|
72
|
-
console.log(` Share read-only to an IDE: ${outcome.
|
|
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") {
|
|
@@ -135,6 +135,7 @@ async function runNonInteractiveLink(options) {
|
|
|
135
135
|
projectName: match.projectName,
|
|
136
136
|
renamed: false,
|
|
137
137
|
settingsPath,
|
|
138
|
+
signedInAs: oauthResult.email,
|
|
138
139
|
...artifacts,
|
|
139
140
|
});
|
|
140
141
|
return;
|
|
@@ -189,6 +190,7 @@ async function runNonInteractiveLink(options) {
|
|
|
189
190
|
projectName: name,
|
|
190
191
|
renamed: false,
|
|
191
192
|
settingsPath,
|
|
193
|
+
signedInAs: oauthResult.email,
|
|
192
194
|
...artifacts,
|
|
193
195
|
});
|
|
194
196
|
return;
|
|
@@ -227,6 +229,7 @@ async function runNonInteractiveLink(options) {
|
|
|
227
229
|
projectName,
|
|
228
230
|
renamed,
|
|
229
231
|
settingsPath,
|
|
232
|
+
signedInAs: oauthResult.email,
|
|
230
233
|
...artifacts,
|
|
231
234
|
});
|
|
232
235
|
}
|
|
@@ -262,7 +265,7 @@ async function mintShareArtifacts(client, teamId, projectId) {
|
|
|
262
265
|
const share = (await client.mutation(api.projectSecrets.mutations.ensureShareToken,
|
|
263
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.
|
|
264
267
|
{ target: { type: "project", id: projectId } }));
|
|
265
|
-
artifacts.
|
|
268
|
+
artifacts.shareSyncCommand = `npx -y @popoverai/dotrequirements@latest sync --share ${share.token}`;
|
|
266
269
|
}
|
|
267
270
|
catch {
|
|
268
271
|
// LINK-13.3: connection already succeeded — omit rather than fail
|
|
@@ -402,8 +405,11 @@ async function runInteractiveLink(options) {
|
|
|
402
405
|
// LINK-7: Optionally pull requirements
|
|
403
406
|
const shouldPull = await promptConfirm("Pull requirements from cloud now?", true);
|
|
404
407
|
if (shouldPull) {
|
|
405
|
-
console.log("\
|
|
406
|
-
await
|
|
408
|
+
console.log("\nSyncing requirements...");
|
|
409
|
+
await syncCommand([], {
|
|
410
|
+
cloudContributes: true,
|
|
411
|
+
project: projectSlugOrId,
|
|
412
|
+
});
|
|
407
413
|
}
|
|
408
414
|
// LINK-7.2: Success message
|
|
409
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
|