@mytegroupinc/myte-core 0.0.24 → 0.0.26
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 +107 -0
- package/cli.js +420 -123
- package/package.json +3 -5
package/README.md
CHANGED
|
@@ -15,6 +15,7 @@ This package exists so the public wrapper can stay small and versioned cleanly.
|
|
|
15
15
|
- `MYTE_API_KEY` for project-scoped commands
|
|
16
16
|
- `MYTEAI_API_KEY` for `myte ai`
|
|
17
17
|
- `git` only when using `query --with-diff`
|
|
18
|
+
- No runtime npm dependencies. The CLI uses Node 18+ built-ins for env loading, argument parsing, HTTP, and local context serialization.
|
|
18
19
|
|
|
19
20
|
## Behavior Summary
|
|
20
21
|
|
|
@@ -25,6 +26,110 @@ This package exists so the public wrapper can stay small and versioned cleanly.
|
|
|
25
26
|
- `query --with-diff` requires project repos to be configured for diff collection and fails fast when no matching local project repo can be resolved.
|
|
26
27
|
- Public package documentation is intentionally minimal. Internal rollout and design notes are not part of the npm package contract.
|
|
27
28
|
|
|
29
|
+
## Agent Usage Contract
|
|
30
|
+
|
|
31
|
+
Coding agents should treat `MYTE_API_KEY` as a project-scoped Project Assistant key:
|
|
32
|
+
|
|
33
|
+
- Load it from the workspace `.env` or environment. `MYTE_PROJECT_API_KEY` is accepted as a compatibility fallback.
|
|
34
|
+
- Call `myte config --json` first when project identity or local repo detection is uncertain.
|
|
35
|
+
- Hydrate mission context with `bootstrap` first instead of scraping the web UI. `bootstrap` writes mission cards and mission suggestion thread state.
|
|
36
|
+
- Use `suggestions sync` only when you need to refresh mission review-thread state without refreshing the full board.
|
|
37
|
+
- Use `query --with-diff` for project-scoped code review, planning, and implementation questions.
|
|
38
|
+
- Use `create-prd` only with reviewed markdown files. The markdown file body is the PRD document; `description` is only the short board/card summary.
|
|
39
|
+
- Use `feedback status|edit|assign|archive` for reviewable local artifacts, `feedback submit` for owner review, and `feedback review` for owner/delegate decisions.
|
|
40
|
+
- Use `suggestions create` for both existing-mission edits and new-mission proposals. The only valid `change_type` values are `update` and `create`.
|
|
41
|
+
- Use `suggestions revise` only against an existing `suggestion_id`; do not send `change_type` on revisions because the backend keeps the thread's original type.
|
|
42
|
+
- Use `suggestions review` for owner/elevated mission-review decisions: `approve`, `request_changes`, or `reject`.
|
|
43
|
+
- Use `mission archive` and `mission restore` for lifecycle archive/restore. Do not send `Archived` through `mission status`.
|
|
44
|
+
- Use `feedback move` only when a direct audited state move is intended and include a concrete `--reason`.
|
|
45
|
+
- Use `update-team` when an agent needs to leave a project-level implementation note or verification comment.
|
|
46
|
+
|
|
47
|
+
Do not use `MYTE_API_KEY` against account/JWT web endpoints. The direct project-key surface is `/api/project-assistant/*`.
|
|
48
|
+
|
|
49
|
+
PRD document contract:
|
|
50
|
+
|
|
51
|
+
- Always put the complete PRD in the markdown file passed to `myte create-prd`.
|
|
52
|
+
- `title` maps to the feedback/board title.
|
|
53
|
+
- `description` maps to the short feedback/card summary and must not contain the full PRD.
|
|
54
|
+
- Raw markdown uploads send `{ title, description?, prd_markdown }`.
|
|
55
|
+
- `myte-kanban` uploads send `{ ticket_markdown }`, where the leading JSON block contains metadata and the remaining markdown is the PRD body.
|
|
56
|
+
- The backend stores the markdown as `prd_markdown`, mirrors it as PRD text for search/sync, generates a DOCX attachment from it, and marks `prd_format=markdown`.
|
|
57
|
+
- The UI renders the PRD from stored PRD text/document content, not from `description`.
|
|
58
|
+
|
|
59
|
+
Feedback comment support:
|
|
60
|
+
|
|
61
|
+
- `feedback-sync` includes existing feedback comment turns in local context.
|
|
62
|
+
- Feedback-specific comment creation exists in the web backend at `/api/feedbacks/<feedback_id>/comments`, protected by JWT/project assignment.
|
|
63
|
+
- There is no project-key CLI command for `myte feedback comment` yet. That requires a Project Assistant feedback-comment route plus npm command before agents should rely on it.
|
|
64
|
+
|
|
65
|
+
## Mission Action Map
|
|
66
|
+
|
|
67
|
+
| Action | Command / Surface | Contract |
|
|
68
|
+
| --- | --- | --- |
|
|
69
|
+
| Sync mission cards and review threads | `myte bootstrap --json` | Refreshes `MyteCommandCenter/data/missions/*.yml`, project structure, and `MyteCommandCenter/data/mission-ops.yml`. |
|
|
70
|
+
| Sync only mission review threads | `myte suggestions sync --json` | Refreshes `MyteCommandCenter/data/mission-ops.yml` while preserving local workspace drafts. |
|
|
71
|
+
| Suggest edit to existing mission | `myte suggestions create` with `change_type: update` and `mission_id` | Creates or appends to an active review thread; live mission is unchanged until approval. |
|
|
72
|
+
| Suggest new mission | `myte suggestions create` with `change_type: create` | Requires `change_set.title` and `change_set.description`; creates a review thread only; the mission card is created only after approval. |
|
|
73
|
+
| Revise a suggestion | `myte suggestions revise` with `suggestion_id` | Adds another revision to the same thread. Do not include `change_type`. |
|
|
74
|
+
| Review a suggestion | `myte suggestions review` with `review_action: approve|request_changes|reject` | Project Owner or elevated mission-review delegate required. Approval mutates/creates the mission once; reject archives the thread only; request_changes keeps it actionable. |
|
|
75
|
+
| Update mission status | `myte mission status --mission-ids "M001" --status todo|in_progress|done` | Project-key surface updates canonical mission status for active cards. |
|
|
76
|
+
| Archive mission | `myte mission archive --mission-ids "M001[,M002]" --reason "..."` | Project Owner or elevated delegate required. Sets `Archived`, removes cards from normal bootstrap/board state, and writes mission activity. |
|
|
77
|
+
| Restore mission | `myte mission restore --mission-ids "M001[,M002]" --status todo|in_progress|done --reason "..."` | Project Owner or elevated delegate required. Restores archived cards to an active status and writes mission activity. |
|
|
78
|
+
|
|
79
|
+
Create payloads:
|
|
80
|
+
|
|
81
|
+
```yaml
|
|
82
|
+
items:
|
|
83
|
+
- change_type: update
|
|
84
|
+
mission_id: M001
|
|
85
|
+
change_description: Tighten acceptance criteria
|
|
86
|
+
change_set:
|
|
87
|
+
acceptance_criteria:
|
|
88
|
+
- The API returns a stable error code for invalid input.
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
```yaml
|
|
92
|
+
items:
|
|
93
|
+
- change_type: create
|
|
94
|
+
change_description: Add observability mission
|
|
95
|
+
change_set:
|
|
96
|
+
title: Add mission observability
|
|
97
|
+
description: Track mission lifecycle actions with safe audit metadata.
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Revise payload:
|
|
101
|
+
|
|
102
|
+
```yaml
|
|
103
|
+
items:
|
|
104
|
+
- suggestion_id: 507f1f77bcf86cd799439302
|
|
105
|
+
expected_revision: 1
|
|
106
|
+
change_description: Revise after review
|
|
107
|
+
change_set:
|
|
108
|
+
description: Updated proposed mission description.
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
YAML note: quote scalar values that contain `:` or other YAML-significant punctuation.
|
|
112
|
+
|
|
113
|
+
## Finding Mission And Suggestion IDs
|
|
114
|
+
|
|
115
|
+
Run this first:
|
|
116
|
+
|
|
117
|
+
```powershell
|
|
118
|
+
myte bootstrap --json
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Then read:
|
|
122
|
+
|
|
123
|
+
| Needed Value | Local Source |
|
|
124
|
+
| --- | --- |
|
|
125
|
+
| Existing mission business id | `MyteCommandCenter/data/missions/*.yml` -> `mission_id`, for example `M001`. |
|
|
126
|
+
| Pending suggestion id | `MyteCommandCenter/data/mission-ops.yml` -> `queue[].suggestion_id` or `threads[].suggestion_id`. |
|
|
127
|
+
| Existing suggestion latest revision | `MyteCommandCenter/data/mission-ops.yml` -> `threads[].latest_revision` or `threads[].latest_server_revision.revision`. |
|
|
128
|
+
| Existing suggestion review revision | `MyteCommandCenter/data/mission-ops.yml` -> `threads[].review_revision`. |
|
|
129
|
+
| Thread type | `MyteCommandCenter/data/mission-ops.yml` -> `threads[].change_type`, either `update` or `create`. |
|
|
130
|
+
|
|
131
|
+
Agent rule: never guess `suggestion_id`. If `mission-ops.yml` is missing or stale, run `myte bootstrap --json` again. Use `myte suggestions sync --json` only for a narrow refresh after a mutation.
|
|
132
|
+
|
|
28
133
|
## Feedback Action Map
|
|
29
134
|
|
|
30
135
|
| Command | What It Does | Governance |
|
|
@@ -72,6 +177,8 @@ Mutation routes:
|
|
|
72
177
|
- `POST /api/project-assistant/query`
|
|
73
178
|
- `POST /api/project-assistant/run-qaqc`
|
|
74
179
|
- `POST /api/project-assistant/mission-status-update`
|
|
180
|
+
- `POST /api/project-assistant/mission-archive`
|
|
181
|
+
- `POST /api/project-assistant/mission-restore`
|
|
75
182
|
- `POST /api/project-assistant/project-comment`
|
|
76
183
|
- `POST /api/project-assistant/update-owner`
|
|
77
184
|
- `POST /api/project-assistant/client-update-drafts`
|
package/cli.js
CHANGED
|
@@ -46,25 +46,25 @@ function findEnvPath(startDir) {
|
|
|
46
46
|
|
|
47
47
|
function loadEnv() {
|
|
48
48
|
const envPath = findEnvPath(process.cwd());
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return;
|
|
54
|
-
|
|
55
|
-
if (
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
}
|
|
49
|
+
if (!envPath || !fs.existsSync(envPath)) return;
|
|
50
|
+
const content = fs.readFileSync(envPath, "utf8");
|
|
51
|
+
content.split(/\r?\n/).forEach((line) => {
|
|
52
|
+
const trimmed = String(line || "").trim();
|
|
53
|
+
if (!trimmed || trimmed.startsWith("#")) return;
|
|
54
|
+
const idx = trimmed.indexOf("=");
|
|
55
|
+
if (idx === -1) return;
|
|
56
|
+
const key = trimmed.slice(0, idx).trim();
|
|
57
|
+
let val = trimmed.slice(idx + 1).trim();
|
|
58
|
+
if (
|
|
59
|
+
(val.startsWith('"') && val.endsWith('"')) ||
|
|
60
|
+
(val.startsWith("'") && val.endsWith("'"))
|
|
61
|
+
) {
|
|
62
|
+
val = val.slice(1, -1);
|
|
63
|
+
}
|
|
64
|
+
if (key && !(key in process.env)) {
|
|
65
|
+
process.env[key] = val;
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
function splitCommand(argv) {
|
|
@@ -75,6 +75,12 @@ function splitCommand(argv) {
|
|
|
75
75
|
if (argv[1] === "status") {
|
|
76
76
|
return { command: "mission-status", rest: argv.slice(2) };
|
|
77
77
|
}
|
|
78
|
+
if (argv[1] === "archive") {
|
|
79
|
+
return { command: "mission-archive", rest: argv.slice(2) };
|
|
80
|
+
}
|
|
81
|
+
if (argv[1] === "restore" || argv[1] === "unarchive") {
|
|
82
|
+
return { command: "mission-restore", rest: argv.slice(2) };
|
|
83
|
+
}
|
|
78
84
|
return { command: "mission", rest: argv.slice(1) };
|
|
79
85
|
}
|
|
80
86
|
|
|
@@ -113,105 +119,60 @@ function splitCommand(argv) {
|
|
|
113
119
|
}
|
|
114
120
|
|
|
115
121
|
function parseArgs(argv) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
"target-contact-ids",
|
|
144
|
-
"status",
|
|
145
|
-
"source",
|
|
146
|
-
"feedback-id",
|
|
147
|
-
"request-id",
|
|
148
|
-
"event-id",
|
|
149
|
-
"version-id",
|
|
150
|
-
"compare-to",
|
|
151
|
-
"action",
|
|
152
|
-
"decision",
|
|
153
|
-
"to-state",
|
|
154
|
-
"from-state",
|
|
155
|
-
"reason",
|
|
156
|
-
"review-action",
|
|
157
|
-
"idempotency-key",
|
|
158
|
-
"user-id",
|
|
159
|
-
"assigned-user-id",
|
|
160
|
-
"due-date",
|
|
161
|
-
"priority",
|
|
162
|
-
"review-note",
|
|
163
|
-
"final-file",
|
|
164
|
-
"tags",
|
|
165
|
-
"tag",
|
|
166
|
-
"mission-ids",
|
|
167
|
-
"client-session-id",
|
|
168
|
-
],
|
|
169
|
-
alias: {
|
|
170
|
-
q: "query",
|
|
171
|
-
d: "with-diff",
|
|
172
|
-
c: "context",
|
|
173
|
-
h: "help",
|
|
174
|
-
},
|
|
175
|
-
default: {
|
|
176
|
-
fetch: true,
|
|
177
|
-
},
|
|
178
|
-
});
|
|
179
|
-
parsed.__raw = Array.isArray(argv) ? [...argv] : [];
|
|
180
|
-
return parsed;
|
|
181
|
-
} catch (err) {
|
|
182
|
-
const parsed = { _: [] };
|
|
183
|
-
for (let i = 0; i < argv.length; i += 1) {
|
|
184
|
-
const token = argv[i];
|
|
185
|
-
if (token.startsWith("--no-")) {
|
|
186
|
-
const key = token.slice(5);
|
|
187
|
-
parsed[key] = false;
|
|
122
|
+
const parsed = { _: [], fetch: true };
|
|
123
|
+
const aliases = { q: "query", d: "with-diff", c: "context", h: "help" };
|
|
124
|
+
const setValue = (key, value) => {
|
|
125
|
+
const finalKey = aliases[key] || key;
|
|
126
|
+
if (parsed[finalKey] === undefined) {
|
|
127
|
+
parsed[finalKey] = value;
|
|
128
|
+
} else if (Array.isArray(parsed[finalKey])) {
|
|
129
|
+
parsed[finalKey].push(value);
|
|
130
|
+
} else {
|
|
131
|
+
parsed[finalKey] = [parsed[finalKey], value];
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
136
|
+
const token = argv[i];
|
|
137
|
+
if (token === "--") {
|
|
138
|
+
parsed._.push(...argv.slice(i + 1));
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
if (token.startsWith("--no-")) {
|
|
142
|
+
setValue(token.slice(5), false);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (token.startsWith("--")) {
|
|
146
|
+
const eqIdx = token.indexOf("=");
|
|
147
|
+
if (eqIdx !== -1) {
|
|
148
|
+
setValue(token.slice(2, eqIdx), token.slice(eqIdx + 1));
|
|
188
149
|
continue;
|
|
189
150
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
i += 1;
|
|
196
|
-
} else {
|
|
197
|
-
parsed[key] = true;
|
|
198
|
-
}
|
|
199
|
-
} else if (token.startsWith("-")) {
|
|
200
|
-
const key = token.slice(1);
|
|
201
|
-
const next = argv[i + 1];
|
|
202
|
-
if (next && !next.startsWith("-")) {
|
|
203
|
-
parsed[key] = next;
|
|
204
|
-
i += 1;
|
|
205
|
-
} else {
|
|
206
|
-
parsed[key] = true;
|
|
207
|
-
}
|
|
151
|
+
const key = token.slice(2);
|
|
152
|
+
const next = argv[i + 1];
|
|
153
|
+
if (next !== undefined && !next.startsWith("-")) {
|
|
154
|
+
setValue(key, next);
|
|
155
|
+
i += 1;
|
|
208
156
|
} else {
|
|
209
|
-
|
|
157
|
+
setValue(key, true);
|
|
210
158
|
}
|
|
159
|
+
continue;
|
|
211
160
|
}
|
|
212
|
-
|
|
213
|
-
|
|
161
|
+
if (token.startsWith("-") && token.length > 1) {
|
|
162
|
+
const key = token.slice(1);
|
|
163
|
+
const next = argv[i + 1];
|
|
164
|
+
if (next !== undefined && !next.startsWith("-")) {
|
|
165
|
+
setValue(key, next);
|
|
166
|
+
i += 1;
|
|
167
|
+
} else {
|
|
168
|
+
setValue(key, true);
|
|
169
|
+
}
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
parsed._.push(token);
|
|
214
173
|
}
|
|
174
|
+
parsed.__raw = Array.isArray(argv) ? [...argv] : [];
|
|
175
|
+
return parsed;
|
|
215
176
|
}
|
|
216
177
|
|
|
217
178
|
function printHelp() {
|
|
@@ -225,6 +186,8 @@ function printHelp() {
|
|
|
225
186
|
" myte bootstrap [--output-dir ./MyteCommandCenter] [--json]",
|
|
226
187
|
" myte run-qaqc --mission-ids \"M001[,M002...]\" [--wait] [--sync] [--force] [--json]",
|
|
227
188
|
" myte mission status --mission-ids \"M001[,M002...]\" --status todo|in_progress|done [--json]",
|
|
189
|
+
" myte mission archive --mission-ids \"M001[,M002...]\" [--reason \"...\"] [--json]",
|
|
190
|
+
" myte mission restore --mission-ids \"M001[,M002...]\" [--status todo|in_progress|done] [--reason \"...\"] [--json]",
|
|
228
191
|
" myte sync-qaqc [--output-dir ./MyteCommandCenter] [--json]",
|
|
229
192
|
" myte suggestions sync [--output-dir ./MyteCommandCenter] [--json]",
|
|
230
193
|
" myte suggestions create [--file ./payload.yml] [--no-sync] [--json]",
|
|
@@ -272,8 +235,10 @@ function printHelp() {
|
|
|
272
235
|
"bootstrap contract:",
|
|
273
236
|
" - Run from any workspace where you want local MyteCommandCenter data written",
|
|
274
237
|
" - Writes MyteCommandCenter/data/project.yml plus phases, epics, stories, and missions locally",
|
|
238
|
+
" - Also refreshes mission review threads into MyteCommandCenter/data/mission-ops.yml by default",
|
|
275
239
|
" - Uses the project-scoped bootstrap snapshot from the Myte API",
|
|
276
240
|
" - Mission cards include richer execution context like complexity, estimated_hours, due_date, subtasks, technical_requirements, resources_needed, labels, and normalized test_cases",
|
|
241
|
+
" - Use --no-suggestions only when you intentionally want mission cards without mission review-thread state",
|
|
277
242
|
"",
|
|
278
243
|
"sync-qaqc contract:",
|
|
279
244
|
" - Run from any workspace where you want local MyteCommandCenter data written",
|
|
@@ -284,7 +249,10 @@ function printHelp() {
|
|
|
284
249
|
" - suggestions sync pulls /api/project-assistant/suggestions and writes only MyteCommandCenter/data/mission-ops.yml",
|
|
285
250
|
" - sync preserves top-level workspace blocks and per-thread workspace.<actor_scope> drafts from the existing mission-ops file",
|
|
286
251
|
" - create reads --file YAML/JSON or MyteCommandCenter/data/mission-ops.yml workspace.<actor_scope>.draft_submissions[]",
|
|
252
|
+
" - create change_type values are exactly: update, create",
|
|
253
|
+
" - create update items require mission_id; create new-mission items require change_set.title and change_set.description",
|
|
287
254
|
" - revise reads --file YAML/JSON or threads[].workspace.<actor_scope> blocks marked with draft_status=submit|ready|pending_submit",
|
|
255
|
+
" - revise does not accept a new change_type; it revises an existing suggestion_id and keeps that thread's change_type",
|
|
288
256
|
" - review reads --file YAML/JSON or threads[].workspace.<actor_scope> review_action / final_change_set local review blocks",
|
|
289
257
|
" - create/revise/review sync mission-ops by default after success unless --no-sync is set",
|
|
290
258
|
"",
|
|
@@ -298,13 +266,22 @@ function printHelp() {
|
|
|
298
266
|
" - Accepts mission business ids and normalizes status aliases like todo, in_progress, and done",
|
|
299
267
|
" - Canonical mission statuses are exactly: Todo, In Progress, Done",
|
|
300
268
|
" - This only changes mission state. It does not queue QAQC and it does not sync MyteCommandCenter/data/qaqc.yml",
|
|
269
|
+
" - Use mission archive/restore for lifecycle archival; do not send Archived through mission status",
|
|
270
|
+
"",
|
|
271
|
+
"mission archive/restore contract:",
|
|
272
|
+
" - Archive calls /api/project-assistant/mission-archive and restore calls /api/project-assistant/mission-restore",
|
|
273
|
+
" - Both require Project Owner or elevated owner_delegate authority on the authenticated project key",
|
|
274
|
+
" - Archive hides cards from normal bootstrap/board state; restore brings them back to Todo by default",
|
|
275
|
+
" - Missions are recoverable. The project-key surface does not hard-delete mission cards",
|
|
301
276
|
"",
|
|
302
277
|
"create-prd contract:",
|
|
303
278
|
" - Required: valid MYTE_API_KEY, PRD markdown body, title",
|
|
304
279
|
" - Accepts one file or many files per command; multi-file uploads are sent in one deterministic batch request",
|
|
305
280
|
" - Title source: myte-kanban.title, first # heading, or --title",
|
|
306
|
-
" - Description source: myte-kanban.description or --description",
|
|
307
|
-
" -
|
|
281
|
+
" - Description source: myte-kanban.description or --description; this is a short feedback/card summary only",
|
|
282
|
+
" - Never put the full PRD in description. The complete PRD must live in the markdown file body",
|
|
283
|
+
" - Each item carries the full PRD markdown blob as prd_markdown/ticket_markdown",
|
|
284
|
+
" - Backend stores that blob as the renderable PRD document source, mirrors it for PRD text search/sync, and generates a DOCX attachment",
|
|
308
285
|
" - PRD DOCX content: the markdown body is stored verbatim",
|
|
309
286
|
"",
|
|
310
287
|
"update-team contract:",
|
|
@@ -327,6 +304,7 @@ function printHelp() {
|
|
|
327
304
|
" - Syncs pending feedback by default so local Command Center data stays focused on active work",
|
|
328
305
|
" - Writes project feedback metadata and conversation turns into MyteCommandCenter/data/feedback.yml",
|
|
329
306
|
" - Stores full PRD context in MyteCommandCenter/PRD/feedback-sync/*.md and points to those files from feedback.yml",
|
|
307
|
+
" - Reads existing feedback comments; direct project-key creation of feedback-specific comments is not exposed yet",
|
|
330
308
|
"",
|
|
331
309
|
"feedback review contract:",
|
|
332
310
|
" - Draft commands write review artifacts under MyteCommandCenter/reviews/feedback/*.yml for local IDE diff review",
|
|
@@ -395,6 +373,7 @@ function printHelp() {
|
|
|
395
373
|
" --with-prd-text Include extracted PRD text so local PRD files are materialized during feedback-sync (default: on)",
|
|
396
374
|
" --no-with-prd-text Skip PRD text download and write only feedback metadata/comment turns",
|
|
397
375
|
" --mission-ids <ids> Comma-separated mission business ids for run-qaqc or mission status (quote multi-id values on PowerShell)",
|
|
376
|
+
" --reason <text> Optional reason for governed mission archive/restore or feedback changes",
|
|
398
377
|
" --actor-scope <id> Actor workspace key inside mission-ops.yml (defaults to machine-cwd slug)",
|
|
399
378
|
" --wait Poll batch status until terminal completion for run-qaqc",
|
|
400
379
|
" --sync After run-qaqc completes, refresh local QAQC file",
|
|
@@ -414,6 +393,8 @@ function printHelp() {
|
|
|
414
393
|
" myte suggestions review --file ./review.yml",
|
|
415
394
|
" myte run-qaqc --mission-ids \"M001,M002\" --wait --sync",
|
|
416
395
|
" myte mission status --mission-ids \"M001,M002\" --status done",
|
|
396
|
+
" myte mission archive --mission-ids \"M001\" --reason \"Duplicate disposable test mission\"",
|
|
397
|
+
" myte mission restore --mission-ids \"M001\" --status todo",
|
|
417
398
|
" myte bootstrap --output-dir ./MyteCommandCenter",
|
|
418
399
|
" myte sync-qaqc",
|
|
419
400
|
" myte update-team \"Backend deploy completed; QAQC rerun queued.\"",
|
|
@@ -636,8 +617,7 @@ function sleep(ms) {
|
|
|
636
617
|
|
|
637
618
|
async function getFetch() {
|
|
638
619
|
if (typeof fetch !== "undefined") return fetch;
|
|
639
|
-
|
|
640
|
-
return mod.default;
|
|
620
|
+
throw new Error("Global fetch is unavailable. myte requires Node 18+.");
|
|
641
621
|
}
|
|
642
622
|
|
|
643
623
|
function normalizeApiBase(baseRaw) {
|
|
@@ -2103,6 +2083,34 @@ async function createMissionStatusUpdate({ apiBase, key, timeoutMs, payload, ide
|
|
|
2103
2083
|
return body.data || {};
|
|
2104
2084
|
}
|
|
2105
2085
|
|
|
2086
|
+
async function createMissionArchiveUpdate({ apiBase, key, timeoutMs, endpoint, payload, idempotencyKey, clientSessionId }) {
|
|
2087
|
+
const fetchFn = await getFetch();
|
|
2088
|
+
const url = `${apiBase}${endpoint}`;
|
|
2089
|
+
const { resp, body } = await fetchJsonWithTimeout(
|
|
2090
|
+
fetchFn,
|
|
2091
|
+
url,
|
|
2092
|
+
{
|
|
2093
|
+
method: "POST",
|
|
2094
|
+
headers: {
|
|
2095
|
+
"Content-Type": "application/json",
|
|
2096
|
+
Authorization: `Bearer ${key}`,
|
|
2097
|
+
"X-Idempotency-Key": String(idempotencyKey || "").trim(),
|
|
2098
|
+
...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
|
|
2099
|
+
},
|
|
2100
|
+
body: JSON.stringify(payload),
|
|
2101
|
+
},
|
|
2102
|
+
timeoutMs
|
|
2103
|
+
);
|
|
2104
|
+
|
|
2105
|
+
if (!resp.ok || body.status !== "success") {
|
|
2106
|
+
const msg = body?.message || `Mission archive request failed (${resp.status})`;
|
|
2107
|
+
const err = new Error(msg);
|
|
2108
|
+
err.status = resp.status;
|
|
2109
|
+
throw err;
|
|
2110
|
+
}
|
|
2111
|
+
return body.data || {};
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2106
2114
|
async function fetchRunQaqcBatchStatus({ apiBase, key, timeoutMs, batchId }) {
|
|
2107
2115
|
const fetchFn = await getFetch();
|
|
2108
2116
|
const url = `${apiBase}/project-assistant/run-qaqc/${encodeURIComponent(String(batchId || ""))}`;
|
|
@@ -2437,6 +2445,109 @@ async function runMissionStatus(args) {
|
|
|
2437
2445
|
}
|
|
2438
2446
|
}
|
|
2439
2447
|
|
|
2448
|
+
async function runMissionArchiveCommand(args, { restore = false } = {}) {
|
|
2449
|
+
const key = getProjectApiKey();
|
|
2450
|
+
const commandLabel = restore ? "mission restore" : "mission archive";
|
|
2451
|
+
if (!key) {
|
|
2452
|
+
console.error("Missing MYTE_API_KEY (project key) in environment/.env");
|
|
2453
|
+
process.exit(1);
|
|
2454
|
+
}
|
|
2455
|
+
|
|
2456
|
+
const missionIds = parseMissionIdsArg(args);
|
|
2457
|
+
if (!missionIds.length) {
|
|
2458
|
+
console.error(`Missing --mission-ids for \`myte ${commandLabel}\`.`);
|
|
2459
|
+
printHelp();
|
|
2460
|
+
process.exit(1);
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2463
|
+
const payload = {
|
|
2464
|
+
mission_ids: missionIds,
|
|
2465
|
+
};
|
|
2466
|
+
if (restore) {
|
|
2467
|
+
const restoreStatus = normalizeMissionStatusInput(firstNonEmptyString(args.status, args["restore-status"], args.restoreStatus, args.restore_status) || "todo");
|
|
2468
|
+
if (!restoreStatus) {
|
|
2469
|
+
console.error("Missing or invalid --status for `myte mission restore`. Use todo, in_progress, or done.");
|
|
2470
|
+
printHelp();
|
|
2471
|
+
process.exit(1);
|
|
2472
|
+
}
|
|
2473
|
+
payload.restore_status = restoreStatus;
|
|
2474
|
+
}
|
|
2475
|
+
const reason = firstNonEmptyString(args.reason);
|
|
2476
|
+
if (reason) payload.reason = reason;
|
|
2477
|
+
const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
|
|
2478
|
+
if (clientSessionId) payload.client_session_id = clientSessionId;
|
|
2479
|
+
|
|
2480
|
+
const operation = restore ? "mission-restore" : "mission-archive";
|
|
2481
|
+
const idempotencyKey = resolveProjectMutationIdempotencyKey({
|
|
2482
|
+
args,
|
|
2483
|
+
operation,
|
|
2484
|
+
payload,
|
|
2485
|
+
});
|
|
2486
|
+
|
|
2487
|
+
if (args["print-context"] || args.printContext || args["dry-run"] || args.dryRun) {
|
|
2488
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
2489
|
+
return;
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
const timeoutMs = resolveTimeoutMs(args);
|
|
2493
|
+
const apiBase = resolveApiBase(args);
|
|
2494
|
+
const endpoint = restore ? "/project-assistant/mission-restore" : "/project-assistant/mission-archive";
|
|
2495
|
+
|
|
2496
|
+
let data;
|
|
2497
|
+
try {
|
|
2498
|
+
data = await createMissionArchiveUpdate({
|
|
2499
|
+
apiBase,
|
|
2500
|
+
key,
|
|
2501
|
+
timeoutMs,
|
|
2502
|
+
endpoint,
|
|
2503
|
+
payload,
|
|
2504
|
+
idempotencyKey,
|
|
2505
|
+
clientSessionId,
|
|
2506
|
+
});
|
|
2507
|
+
} catch (err) {
|
|
2508
|
+
if (err?.name === "AbortError") {
|
|
2509
|
+
console.error(`Request timed out after ${timeoutMs}ms`);
|
|
2510
|
+
} else {
|
|
2511
|
+
console.error(`Mission ${restore ? "restore" : "archive"} failed:`, err?.message || err);
|
|
2512
|
+
}
|
|
2513
|
+
process.exit(1);
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2516
|
+
const output = {
|
|
2517
|
+
project_id: data.project_id || null,
|
|
2518
|
+
requested_count: data.requested_count || missionIds.length,
|
|
2519
|
+
matched_count: data.matched_count || 0,
|
|
2520
|
+
updated_count: data.updated_count || 0,
|
|
2521
|
+
unchanged_count: data.unchanged_count || 0,
|
|
2522
|
+
rejected_count: data.rejected_count || 0,
|
|
2523
|
+
archive_state: data.archive_state || (restore ? "active" : "archived"),
|
|
2524
|
+
new_status: data.new_status || (restore ? "Todo" : "Archived"),
|
|
2525
|
+
missions: Array.isArray(data.missions) ? data.missions : [],
|
|
2526
|
+
};
|
|
2527
|
+
|
|
2528
|
+
if (args.json) {
|
|
2529
|
+
console.log(JSON.stringify(output, null, 2));
|
|
2530
|
+
return;
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
if (output.project_id) console.log(`Project ID: ${output.project_id}`);
|
|
2534
|
+
console.log(`Archive State: ${output.archive_state}`);
|
|
2535
|
+
console.log(`Target Status: ${output.new_status}`);
|
|
2536
|
+
console.log(`Requested: ${output.requested_count}`);
|
|
2537
|
+
console.log(`Matched: ${output.matched_count}`);
|
|
2538
|
+
console.log(`Updated: ${output.updated_count}`);
|
|
2539
|
+
console.log(`Unchanged: ${output.unchanged_count}`);
|
|
2540
|
+
console.log(`Rejected: ${output.rejected_count}`);
|
|
2541
|
+
for (const mission of output.missions) {
|
|
2542
|
+
const missionId = String(mission?.mission_id || "").trim() || "(unknown)";
|
|
2543
|
+
const status = String(mission?.status || "").trim() || "unknown";
|
|
2544
|
+
const detailParts = [];
|
|
2545
|
+
if (mission?.current_status) detailParts.push(`current=${mission.current_status}`);
|
|
2546
|
+
if (mission?.new_status) detailParts.push(`target=${mission.new_status}`);
|
|
2547
|
+
console.log(`- ${missionId}: ${status}${detailParts.length ? ` (${detailParts.join(", ")})` : ""}`);
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2440
2551
|
function formatTargetContacts(contacts) {
|
|
2441
2552
|
const items = Array.isArray(contacts) ? contacts : [];
|
|
2442
2553
|
const formatted = items
|
|
@@ -2722,9 +2833,7 @@ function bootstrapScopedPathId(item, preferredKeys, scopeKeys, fallbackKeys, fal
|
|
|
2722
2833
|
}
|
|
2723
2834
|
|
|
2724
2835
|
function stringifyYaml(value) {
|
|
2725
|
-
|
|
2726
|
-
const YAML = require("yaml");
|
|
2727
|
-
return YAML.stringify(value, { lineWidth: 0 });
|
|
2836
|
+
return `${JSON.stringify(value, null, 2)}\n`;
|
|
2728
2837
|
}
|
|
2729
2838
|
|
|
2730
2839
|
function stableJsonStringify(value) {
|
|
@@ -2741,9 +2850,116 @@ function stableJsonStringify(value) {
|
|
|
2741
2850
|
}
|
|
2742
2851
|
|
|
2743
2852
|
function parseYaml(text) {
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2853
|
+
const raw = String(text || "").trim();
|
|
2854
|
+
if (!raw) return null;
|
|
2855
|
+
if (raw.startsWith("{") || raw.startsWith("[")) {
|
|
2856
|
+
return JSON.parse(raw);
|
|
2857
|
+
}
|
|
2858
|
+
return parseSimpleYaml(raw);
|
|
2859
|
+
}
|
|
2860
|
+
|
|
2861
|
+
function parseScalarYaml(value) {
|
|
2862
|
+
const raw = String(value || "").trim();
|
|
2863
|
+
if (raw === "") return "";
|
|
2864
|
+
if (raw === "null" || raw === "~") return null;
|
|
2865
|
+
if (raw === "true") return true;
|
|
2866
|
+
if (raw === "false") return false;
|
|
2867
|
+
if (/^-?\d+(?:\.\d+)?$/.test(raw)) return Number(raw);
|
|
2868
|
+
if (
|
|
2869
|
+
(raw.startsWith('"') && raw.endsWith('"')) ||
|
|
2870
|
+
(raw.startsWith("'") && raw.endsWith("'"))
|
|
2871
|
+
) {
|
|
2872
|
+
return raw.slice(1, -1);
|
|
2873
|
+
}
|
|
2874
|
+
return raw;
|
|
2875
|
+
}
|
|
2876
|
+
|
|
2877
|
+
function parseSimpleYaml(text) {
|
|
2878
|
+
const root = {};
|
|
2879
|
+
const stack = [{ indent: -1, type: "object", value: root }];
|
|
2880
|
+
const lines = String(text || "").split(/\r?\n/);
|
|
2881
|
+
let lastScalar = null;
|
|
2882
|
+
|
|
2883
|
+
const nextContentLine = (startIndex) => {
|
|
2884
|
+
for (let i = startIndex; i < lines.length; i += 1) {
|
|
2885
|
+
const raw = lines[i];
|
|
2886
|
+
if (!raw || !raw.trim() || raw.trim().startsWith("#")) continue;
|
|
2887
|
+
return raw;
|
|
2888
|
+
}
|
|
2889
|
+
return "";
|
|
2890
|
+
};
|
|
2891
|
+
|
|
2892
|
+
const containerForEmpty = (lineIndex) => {
|
|
2893
|
+
const next = nextContentLine(lineIndex + 1);
|
|
2894
|
+
return next.trim().startsWith("- ") ? [] : {};
|
|
2895
|
+
};
|
|
2896
|
+
|
|
2897
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
2898
|
+
const rawLine = lines[lineIndex];
|
|
2899
|
+
if (!rawLine || !rawLine.trim() || rawLine.trim().startsWith("#")) continue;
|
|
2900
|
+
const indent = rawLine.match(/^ */)[0].length;
|
|
2901
|
+
const line = rawLine.trim();
|
|
2902
|
+
|
|
2903
|
+
while (stack.length > 1 && indent <= stack[stack.length - 1].indent) {
|
|
2904
|
+
stack.pop();
|
|
2905
|
+
}
|
|
2906
|
+
const parent = stack[stack.length - 1].value;
|
|
2907
|
+
|
|
2908
|
+
if (line.startsWith("- ")) {
|
|
2909
|
+
if (!Array.isArray(parent)) {
|
|
2910
|
+
throw new Error("Unsupported YAML list placement.");
|
|
2911
|
+
}
|
|
2912
|
+
const body = line.slice(2).trim();
|
|
2913
|
+
if (!body) {
|
|
2914
|
+
const child = containerForEmpty(lineIndex);
|
|
2915
|
+
parent.push(child);
|
|
2916
|
+
stack.push({ indent, type: Array.isArray(child) ? "array" : "object", value: child });
|
|
2917
|
+
} else if (body.includes(":")) {
|
|
2918
|
+
const [key, ...rest] = body.split(":");
|
|
2919
|
+
const obj = {};
|
|
2920
|
+
const keyName = key.trim();
|
|
2921
|
+
const valueText = rest.join(":").trim();
|
|
2922
|
+
const value = valueText ? parseScalarYaml(valueText) : containerForEmpty(lineIndex);
|
|
2923
|
+
obj[keyName] = value;
|
|
2924
|
+
parent.push(obj);
|
|
2925
|
+
stack.push({ indent, type: "object", value: obj });
|
|
2926
|
+
if (value && typeof value === "object") {
|
|
2927
|
+
lastScalar = null;
|
|
2928
|
+
stack.push({ indent: indent + 2, type: Array.isArray(value) ? "array" : "object", value });
|
|
2929
|
+
} else {
|
|
2930
|
+
lastScalar = { container: obj, key: keyName, indent };
|
|
2931
|
+
}
|
|
2932
|
+
} else {
|
|
2933
|
+
parent.push(parseScalarYaml(body));
|
|
2934
|
+
lastScalar = { container: parent, key: parent.length - 1, indent };
|
|
2935
|
+
}
|
|
2936
|
+
continue;
|
|
2937
|
+
}
|
|
2938
|
+
|
|
2939
|
+
const sep = line.indexOf(":");
|
|
2940
|
+
if (sep === -1) {
|
|
2941
|
+
if (lastScalar && indent > lastScalar.indent && lastScalar.container) {
|
|
2942
|
+
const previous = lastScalar.container[lastScalar.key];
|
|
2943
|
+
if (typeof previous === "string") {
|
|
2944
|
+
lastScalar.container[lastScalar.key] = `${previous} ${parseScalarYaml(line)}`;
|
|
2945
|
+
continue;
|
|
2946
|
+
}
|
|
2947
|
+
}
|
|
2948
|
+
throw new Error(`Unsupported YAML line: ${line}`);
|
|
2949
|
+
}
|
|
2950
|
+
const key = line.slice(0, sep).trim();
|
|
2951
|
+
const valueText = line.slice(sep + 1).trim();
|
|
2952
|
+
const value = valueText ? parseScalarYaml(valueText) : containerForEmpty(lineIndex);
|
|
2953
|
+
parent[key] = value;
|
|
2954
|
+
if (value && typeof value === "object") {
|
|
2955
|
+
lastScalar = null;
|
|
2956
|
+
stack.push({ indent, type: Array.isArray(value) ? "array" : "object", value });
|
|
2957
|
+
} else {
|
|
2958
|
+
lastScalar = { container: parent, key, indent };
|
|
2959
|
+
}
|
|
2960
|
+
}
|
|
2961
|
+
|
|
2962
|
+
return root;
|
|
2747
2963
|
}
|
|
2748
2964
|
|
|
2749
2965
|
function writeYamlFile(filePath, value) {
|
|
@@ -3601,6 +3817,34 @@ function normalizeItemsPayload(rawPayload, label) {
|
|
|
3601
3817
|
throw new Error(`${label} payload must be an object, array, or { items: [...] } envelope.`);
|
|
3602
3818
|
}
|
|
3603
3819
|
|
|
3820
|
+
function validateSuggestionsCreatePayload(payload) {
|
|
3821
|
+
const items = Array.isArray(payload?.items) ? payload.items : [];
|
|
3822
|
+
for (const [index, item] of items.entries()) {
|
|
3823
|
+
if (!isPlainObject(item)) continue;
|
|
3824
|
+
const changeType = String(item.change_type || "").trim().toLowerCase();
|
|
3825
|
+
if (!["update", "create"].includes(changeType)) {
|
|
3826
|
+
throw new Error(`Invalid suggestions create items[${index}].change_type: use update or create.`);
|
|
3827
|
+
}
|
|
3828
|
+
if (changeType === "update" && !String(item.mission_id || "").trim()) {
|
|
3829
|
+
throw new Error(`Invalid suggestions create items[${index}].mission_id: update suggestions require mission_id.`);
|
|
3830
|
+
}
|
|
3831
|
+
if (changeType === "create") {
|
|
3832
|
+
const changeSet = isPlainObject(item.change_set) ? item.change_set : item;
|
|
3833
|
+
if (!String(changeSet.title || "").trim() || !String(changeSet.description || "").trim()) {
|
|
3834
|
+
throw new Error(`Invalid suggestions create items[${index}].change_set: create suggestions require title and description.`);
|
|
3835
|
+
}
|
|
3836
|
+
}
|
|
3837
|
+
}
|
|
3838
|
+
}
|
|
3839
|
+
|
|
3840
|
+
function validateSuggestionsRevisePayload(payload) {
|
|
3841
|
+
const items = Array.isArray(payload?.items) ? payload.items : [];
|
|
3842
|
+
for (const [index, item] of items.entries()) {
|
|
3843
|
+
if (!isPlainObject(item) || item.change_type === undefined) continue;
|
|
3844
|
+
throw new Error(`Invalid suggestions revise items[${index}].change_type: revise keeps the existing thread change_type; provide suggestion_id, change_description/change_set, and expected_revision instead.`);
|
|
3845
|
+
}
|
|
3846
|
+
}
|
|
3847
|
+
|
|
3604
3848
|
function filterWorkspaceDraftsByIndex(drafts, indexesToRemove) {
|
|
3605
3849
|
const removal = new Set(Array.from(indexesToRemove || []).map((value) => Number(value)).filter((value) => Number.isInteger(value) && value > 0));
|
|
3606
3850
|
if (!Array.isArray(drafts) || !removal.size) {
|
|
@@ -4214,6 +4458,8 @@ async function runBootstrap(args) {
|
|
|
4214
4458
|
const wrapperRoot = resolved.root;
|
|
4215
4459
|
const outputDir = args["output-dir"] || args.outputDir || args.output_dir;
|
|
4216
4460
|
const dryRun = Boolean(args["dry-run"] || args.dryRun);
|
|
4461
|
+
const includeSuggestions = resolveBooleanFlag(args, "suggestions", true);
|
|
4462
|
+
const actorScope = resolveActorScope(args, {});
|
|
4217
4463
|
const summary = {
|
|
4218
4464
|
api_base: apiBase,
|
|
4219
4465
|
project_id: snapshot?.project?.id || null,
|
|
@@ -4234,6 +4480,16 @@ async function runBootstrap(args) {
|
|
|
4234
4480
|
snapshot_hash: snapshot.snapshot_hash || null,
|
|
4235
4481
|
generated_at: snapshot.generated_at || null,
|
|
4236
4482
|
dry_run: dryRun,
|
|
4483
|
+
mission_ops: includeSuggestions
|
|
4484
|
+
? {
|
|
4485
|
+
included: true,
|
|
4486
|
+
actor_scope: actorScope,
|
|
4487
|
+
dry_run: dryRun,
|
|
4488
|
+
}
|
|
4489
|
+
: {
|
|
4490
|
+
included: false,
|
|
4491
|
+
skipped: true,
|
|
4492
|
+
},
|
|
4237
4493
|
};
|
|
4238
4494
|
|
|
4239
4495
|
if (dryRun) {
|
|
@@ -4255,6 +4511,27 @@ async function runBootstrap(args) {
|
|
|
4255
4511
|
const writeResult = writeBootstrapSnapshot({ snapshot, wrapperRoot, outputDir });
|
|
4256
4512
|
summary.data_root = writeResult.dataRoot;
|
|
4257
4513
|
|
|
4514
|
+
if (includeSuggestions) {
|
|
4515
|
+
let missionOpsSnapshot;
|
|
4516
|
+
try {
|
|
4517
|
+
missionOpsSnapshot = await fetchSuggestionsSyncSnapshot({ apiBase, key, timeoutMs, actorScope });
|
|
4518
|
+
} catch (err) {
|
|
4519
|
+
console.error("Failed to fetch mission suggestions during bootstrap:", err?.message || err);
|
|
4520
|
+
console.error("Run with --no-suggestions only if you intentionally want mission cards without mission review-thread state.");
|
|
4521
|
+
process.exit(1);
|
|
4522
|
+
}
|
|
4523
|
+
const missionOpsWrite = writeMissionOpsSnapshot({ snapshot: missionOpsSnapshot, wrapperRoot, outputDir });
|
|
4524
|
+
const missionOpsCounts = missionOpsWrite?.manifest?.sync?.counts || {};
|
|
4525
|
+
summary.mission_ops = {
|
|
4526
|
+
included: true,
|
|
4527
|
+
actor_scope: missionOpsWrite?.manifest?.sync?.actor_scope || actorScope,
|
|
4528
|
+
total_threads: Number(missionOpsCounts.total_threads || 0),
|
|
4529
|
+
actionable_threads: Number(missionOpsCounts.actionable_threads || 0),
|
|
4530
|
+
snapshot_hash: missionOpsWrite?.manifest?.snapshot_hash || null,
|
|
4531
|
+
data_root: missionOpsWrite?.dataRoot || writeResult.dataRoot,
|
|
4532
|
+
};
|
|
4533
|
+
}
|
|
4534
|
+
|
|
4258
4535
|
if (args.json) {
|
|
4259
4536
|
console.log(JSON.stringify(summary, null, 2));
|
|
4260
4537
|
return;
|
|
@@ -4267,6 +4544,11 @@ async function runBootstrap(args) {
|
|
|
4267
4544
|
console.log(`Found locally: ${summary.local.found.join(", ") || "(none)"}`);
|
|
4268
4545
|
if (summary.local.missing.length) console.log(`Missing locally: ${summary.local.missing.join(", ")}`);
|
|
4269
4546
|
console.log(`Wrote: phases=${summary.counts.phases}, epics=${summary.counts.epics}, stories=${summary.counts.stories}, missions=${summary.counts.missions}`);
|
|
4547
|
+
if (summary.mission_ops?.included) {
|
|
4548
|
+
console.log(`Wrote mission ops: total_threads=${summary.mission_ops.total_threads}, actionable_threads=${summary.mission_ops.actionable_threads}`);
|
|
4549
|
+
} else {
|
|
4550
|
+
console.log("Skipped mission ops sync.");
|
|
4551
|
+
}
|
|
4270
4552
|
console.log(`Snapshot: ${summary.snapshot_hash || "n/a"}`);
|
|
4271
4553
|
}
|
|
4272
4554
|
|
|
@@ -5315,6 +5597,11 @@ async function buildSuggestionsMutationContext(args, mode) {
|
|
|
5315
5597
|
if (clientSessionId && !payload.client_session_id) {
|
|
5316
5598
|
payload.client_session_id = clientSessionId;
|
|
5317
5599
|
}
|
|
5600
|
+
if (mode === "create") {
|
|
5601
|
+
validateSuggestionsCreatePayload(payload);
|
|
5602
|
+
} else if (mode === "revise") {
|
|
5603
|
+
validateSuggestionsRevisePayload(payload);
|
|
5604
|
+
}
|
|
5318
5605
|
const idempotencyKey = resolveSuggestionsIdempotencyKey({
|
|
5319
5606
|
args,
|
|
5320
5607
|
mode,
|
|
@@ -5920,7 +6207,7 @@ async function main() {
|
|
|
5920
6207
|
process.exit(1);
|
|
5921
6208
|
}
|
|
5922
6209
|
if (command === "mission") {
|
|
5923
|
-
console.error("Unknown mission command. Use `myte mission status
|
|
6210
|
+
console.error("Unknown mission command. Use `myte mission status`, `myte mission archive`, or `myte mission restore`.");
|
|
5924
6211
|
process.exit(1);
|
|
5925
6212
|
}
|
|
5926
6213
|
const args = parseArgs(rest);
|
|
@@ -5954,6 +6241,16 @@ async function main() {
|
|
|
5954
6241
|
return;
|
|
5955
6242
|
}
|
|
5956
6243
|
|
|
6244
|
+
if (command === "mission-archive") {
|
|
6245
|
+
await runMissionArchiveCommand(args, { restore: false });
|
|
6246
|
+
return;
|
|
6247
|
+
}
|
|
6248
|
+
|
|
6249
|
+
if (command === "mission-restore") {
|
|
6250
|
+
await runMissionArchiveCommand(args, { restore: true });
|
|
6251
|
+
return;
|
|
6252
|
+
}
|
|
6253
|
+
|
|
5957
6254
|
if (command === "sync-qaqc" || command === "qaqc-sync") {
|
|
5958
6255
|
await runSyncQaqc(args);
|
|
5959
6256
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mytegroupinc/myte-core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.26",
|
|
4
4
|
"description": "Myte CLI core implementation.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "cli.js",
|
|
@@ -20,10 +20,8 @@
|
|
|
20
20
|
"publishConfig": {
|
|
21
21
|
"access": "public"
|
|
22
22
|
},
|
|
23
|
-
"dependencies": {
|
|
24
|
-
|
|
25
|
-
"minimist": "^1.2.8",
|
|
26
|
-
"node-fetch": "^3.3.2",
|
|
23
|
+
"dependencies": {},
|
|
24
|
+
"devDependencies": {
|
|
27
25
|
"yaml": "^2.8.1"
|
|
28
26
|
}
|
|
29
27
|
}
|