@mytegroupinc/myte-core 0.0.23 → 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 +172 -0
- package/cli.js +442 -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
|
|
|
@@ -24,3 +25,174 @@ This package exists so the public wrapper can stay small and versioned cleanly.
|
|
|
24
25
|
- `feedback validate|apply` remains available for validation and owner-direct apply paths; live authorization, stale checks, and history stay server-side.
|
|
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.
|
|
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
|
+
|
|
133
|
+
## Feedback Action Map
|
|
134
|
+
|
|
135
|
+
| Command | What It Does | Governance |
|
|
136
|
+
| --- | --- | --- |
|
|
137
|
+
| `feedback-sync` | Pulls feedback metadata, comments, and PRD context into `MyteCommandCenter`. | Read-only project-key API call. |
|
|
138
|
+
| `feedback get` | Reads one feedback item's current server snapshot and `snapshot_hash`. | Read-only; backend checks project access. |
|
|
139
|
+
| `feedback status` | Creates a local YAML proposal to change canonical lifecycle state. | No live mutation until `submit` and owner/delegate review, except owner-direct `apply`. |
|
|
140
|
+
| `feedback edit` / `feedback refine` | Creates a local YAML proposal for title, description/body, priority, due date, tags, or notes. | Content changes go through owner-review membrane. |
|
|
141
|
+
| `feedback assign` | Creates a local YAML proposal to change assignee. | Goes through review unless applied through owner-direct path. |
|
|
142
|
+
| `feedback archive` | Creates a local YAML proposal to archive. | Archive is governed by backend owner/delegate capability. |
|
|
143
|
+
| `feedback submit` | Sends a local proposal artifact to the backend as a review request. | Does not mutate live feedback before approval. |
|
|
144
|
+
| `feedback revise` | Resubmits the original submitter's request after `request_changes`. | Only valid for the original submitter and `needs_changes` requests. |
|
|
145
|
+
| `feedback reviews` | Lists review requests or fetches one request by `--request-id`. | Read-only; response includes backend permissions. |
|
|
146
|
+
| `feedback review` | Approves, rejects, requests changes, or cancels a review request. | Backend restricts approval/review decisions to Project Owner or elevated delegate for Feedback scope. |
|
|
147
|
+
| `feedback move` | Moves a card across allowed board states directly with an audit event. | Safe states are direct; governed states such as archive/reject/deploy remain backend-authorized. |
|
|
148
|
+
| `feedback undo` | Reverses an audited board event when there is no conflict. | Backend validates event ownership/project scope and conflict state. |
|
|
149
|
+
| `feedback prd-versions` | Lists retained PRD baselines/revisions for a feedback item. | Read-only; old S3 objects are retained by reference. |
|
|
150
|
+
| `feedback prd-diff` | Fetches backend-generated text diff for PRD versions. | Read-only; backend controls version access. |
|
|
151
|
+
| `feedback history` | Lists audited feedback board/refinement events. | Read-only; backend checks project access. |
|
|
152
|
+
| `feedback validate` | Sends an artifact to backend validation without mutation. | Useful before submit or owner-direct apply. |
|
|
153
|
+
| `feedback apply` | Applies an artifact through the owner-direct/emergency path. | Not the normal collaborator flow; normal flow is `submit` -> `review`. |
|
|
154
|
+
|
|
155
|
+
## Direct Project API Surface
|
|
156
|
+
|
|
157
|
+
All routes use `Authorization: Bearer <MYTE_API_KEY>`. The CLI adds idempotency and client-session headers on mutation routes.
|
|
158
|
+
|
|
159
|
+
Read/sync routes:
|
|
160
|
+
|
|
161
|
+
- `GET /api/project-assistant/config`
|
|
162
|
+
- `GET /api/project-assistant/bootstrap`
|
|
163
|
+
- `GET /api/project-assistant/qaqc-sync`
|
|
164
|
+
- `GET /api/project-assistant/feedback-sync`
|
|
165
|
+
- `GET /api/project-assistant/feedback/<feedback_id>`
|
|
166
|
+
- `GET /api/project-assistant/feedback/<feedback_id>/refinement/history`
|
|
167
|
+
- `GET /api/project-assistant/feedback-review-requests`
|
|
168
|
+
- `GET /api/project-assistant/feedback-review-requests/<request_id>`
|
|
169
|
+
- `GET /api/project-assistant/feedback/<feedback_id>/events`
|
|
170
|
+
- `GET /api/project-assistant/feedback/<feedback_id>/prd/versions`
|
|
171
|
+
- `GET /api/project-assistant/feedback/<feedback_id>/prd/versions/<version_id>/diff`
|
|
172
|
+
- `GET /api/project-assistant/suggestions`
|
|
173
|
+
- `GET /api/project-assistant/run-qaqc/<batch_id>`
|
|
174
|
+
|
|
175
|
+
Mutation routes:
|
|
176
|
+
|
|
177
|
+
- `POST /api/project-assistant/query`
|
|
178
|
+
- `POST /api/project-assistant/run-qaqc`
|
|
179
|
+
- `POST /api/project-assistant/mission-status-update`
|
|
180
|
+
- `POST /api/project-assistant/mission-archive`
|
|
181
|
+
- `POST /api/project-assistant/mission-restore`
|
|
182
|
+
- `POST /api/project-assistant/project-comment`
|
|
183
|
+
- `POST /api/project-assistant/update-owner`
|
|
184
|
+
- `POST /api/project-assistant/client-update-drafts`
|
|
185
|
+
- `POST /api/project-assistant/create-prd`
|
|
186
|
+
- `POST /api/project-assistant/create-prds`
|
|
187
|
+
- `POST /api/project-assistant/feedback/<feedback_id>/refinement/validate`
|
|
188
|
+
- `POST /api/project-assistant/feedback/<feedback_id>/refinement/requests`
|
|
189
|
+
- `POST /api/project-assistant/feedback-review-requests/<request_id>/revise`
|
|
190
|
+
- `POST /api/project-assistant/feedback/<feedback_id>/refinement/apply`
|
|
191
|
+
- `POST /api/project-assistant/feedback-review-requests/<request_id>/review`
|
|
192
|
+
- `POST /api/project-assistant/feedback-review-requests/<request_id>/request-changes`
|
|
193
|
+
- `POST /api/project-assistant/feedback-review-requests/<request_id>/cancel`
|
|
194
|
+
- `POST /api/project-assistant/feedback/<feedback_id>/board-move`
|
|
195
|
+
- `POST /api/project-assistant/feedback/<feedback_id>/events/<event_id>/undo`
|
|
196
|
+
- `POST /api/project-assistant/suggestions`
|
|
197
|
+
- `POST /api/project-assistant/suggestions/revise`
|
|
198
|
+
- `POST /api/project-assistant/suggestions/review`
|
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]",
|
|
@@ -234,8 +197,12 @@ function printHelp() {
|
|
|
234
197
|
" myte update-owner --subject \"<text>\" [--body-markdown \"...\"] [--body-file ./update.md] [--json]",
|
|
235
198
|
" myte update-client --subject \"<text>\" [--body-markdown \"...\"] [--body-file ./update.md] [--target-contact-ids <id1,id2>] [--json]",
|
|
236
199
|
" myte feedback-sync [--status <value>] [--source <value>] [--with-prd-text|--no-with-prd-text] [--output-dir ./MyteCommandCenter] [--json]",
|
|
200
|
+
" myte feedback get --feedback-id <id> [--json]",
|
|
201
|
+
" myte feedback history --feedback-id <id> [--limit 50] [--json]",
|
|
237
202
|
" myte feedback status --feedback-id <id> --status todo|in_progress|in_review|completed|deployed|rejected|archived --reason \"...\"",
|
|
238
203
|
" myte feedback edit --feedback-id <id> [--title \"...\"] [--feedback-text \"...\"] [--priority High] [--reason \"...\"]",
|
|
204
|
+
" myte feedback assign --feedback-id <id> --user-id <id> [--reason \"...\"]",
|
|
205
|
+
" myte feedback archive --feedback-id <id> --reason \"...\"",
|
|
239
206
|
" myte feedback submit --file ./MyteCommandCenter/reviews/feedback/<id>-edit.yml [--json]",
|
|
240
207
|
" myte feedback revise --request-id <id> --file ./MyteCommandCenter/reviews/feedback/<id>-edit.yml [--json]",
|
|
241
208
|
" myte feedback reviews [--status open|terminal|all] [--request-id <id>] [--json]",
|
|
@@ -268,8 +235,10 @@ function printHelp() {
|
|
|
268
235
|
"bootstrap contract:",
|
|
269
236
|
" - Run from any workspace where you want local MyteCommandCenter data written",
|
|
270
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",
|
|
271
239
|
" - Uses the project-scoped bootstrap snapshot from the Myte API",
|
|
272
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",
|
|
273
242
|
"",
|
|
274
243
|
"sync-qaqc contract:",
|
|
275
244
|
" - Run from any workspace where you want local MyteCommandCenter data written",
|
|
@@ -280,7 +249,10 @@ function printHelp() {
|
|
|
280
249
|
" - suggestions sync pulls /api/project-assistant/suggestions and writes only MyteCommandCenter/data/mission-ops.yml",
|
|
281
250
|
" - sync preserves top-level workspace blocks and per-thread workspace.<actor_scope> drafts from the existing mission-ops file",
|
|
282
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",
|
|
283
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",
|
|
284
256
|
" - review reads --file YAML/JSON or threads[].workspace.<actor_scope> review_action / final_change_set local review blocks",
|
|
285
257
|
" - create/revise/review sync mission-ops by default after success unless --no-sync is set",
|
|
286
258
|
"",
|
|
@@ -294,13 +266,22 @@ function printHelp() {
|
|
|
294
266
|
" - Accepts mission business ids and normalizes status aliases like todo, in_progress, and done",
|
|
295
267
|
" - Canonical mission statuses are exactly: Todo, In Progress, Done",
|
|
296
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",
|
|
297
276
|
"",
|
|
298
277
|
"create-prd contract:",
|
|
299
278
|
" - Required: valid MYTE_API_KEY, PRD markdown body, title",
|
|
300
279
|
" - Accepts one file or many files per command; multi-file uploads are sent in one deterministic batch request",
|
|
301
280
|
" - Title source: myte-kanban.title, first # heading, or --title",
|
|
302
|
-
" - Description source: myte-kanban.description or --description",
|
|
303
|
-
" -
|
|
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",
|
|
304
285
|
" - PRD DOCX content: the markdown body is stored verbatim",
|
|
305
286
|
"",
|
|
306
287
|
"update-team contract:",
|
|
@@ -323,17 +304,36 @@ function printHelp() {
|
|
|
323
304
|
" - Syncs pending feedback by default so local Command Center data stays focused on active work",
|
|
324
305
|
" - Writes project feedback metadata and conversation turns into MyteCommandCenter/data/feedback.yml",
|
|
325
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",
|
|
326
308
|
"",
|
|
327
309
|
"feedback review contract:",
|
|
328
310
|
" - Draft commands write review artifacts under MyteCommandCenter/reviews/feedback/*.yml for local IDE diff review",
|
|
311
|
+
" - status/edit/assign/archive/refine create local proposals only; they do not mutate live feedback by themselves",
|
|
329
312
|
" - submit/revise route artifacts through the owner-review membrane before live feedback mutation",
|
|
330
313
|
" - reviews/review expose owner/delegate approval, rejection, request-changes, and cancel decisions",
|
|
331
314
|
" - move/undo operate safe board states directly; governed states remain backend-authorized",
|
|
315
|
+
" - get/history read current feedback snapshots and audited event history through the project-key API",
|
|
332
316
|
" - prd-versions/prd-diff expose retained PRD versions and backend-generated text diffs",
|
|
333
317
|
" - validate/apply send artifacts to /api/project-assistant/feedback/<id>/refinement/* for validation or owner-direct apply",
|
|
334
318
|
" - The backend owns authorization, stale snapshot checks, allowed field/transition rules, and history",
|
|
335
319
|
" - apply is idempotent and does not rewrite local feedback.yml; run feedback-sync after apply to refresh local state",
|
|
336
320
|
"",
|
|
321
|
+
"feedback action map:",
|
|
322
|
+
" - feedback-sync: pull board/PRD context into MyteCommandCenter for IDE work",
|
|
323
|
+
" - get: read one feedback item's current server snapshot and snapshot_hash",
|
|
324
|
+
" - status/edit/assign/archive/refine: create reviewable local YAML artifacts; no live mutation",
|
|
325
|
+
" - submit: submit a local artifact as a pending owner-review request",
|
|
326
|
+
" - revise: resubmit the original submitter's request after request_changes",
|
|
327
|
+
" - reviews: list open/terminal/all requests or inspect one request",
|
|
328
|
+
" - review: approve, reject, request_changes, or cancel; backend restricts this to owner/delegate capability",
|
|
329
|
+
" - move: direct audited board movement for allowed states; governed transitions remain backend-authorized",
|
|
330
|
+
" - undo: reverse an audited board event when conflict checks allow it",
|
|
331
|
+
" - prd-versions: list retained PRD baselines/revisions for a feedback item",
|
|
332
|
+
" - prd-diff: fetch backend-generated text diff between PRD versions",
|
|
333
|
+
" - history: list board/refinement events for audit review",
|
|
334
|
+
" - validate: server-side validation of an artifact without mutation",
|
|
335
|
+
" - apply: owner-direct/emergency apply path; normal collaborative flow is submit -> review",
|
|
336
|
+
"",
|
|
337
337
|
"Options:",
|
|
338
338
|
" --with-diff Include deterministic git diffs (project-scoped; fails fast if no project repos are configured or resolved)",
|
|
339
339
|
" --diff-limit <chars> Truncate diff context to N chars (default: 500000)",
|
|
@@ -373,6 +373,7 @@ function printHelp() {
|
|
|
373
373
|
" --with-prd-text Include extracted PRD text so local PRD files are materialized during feedback-sync (default: on)",
|
|
374
374
|
" --no-with-prd-text Skip PRD text download and write only feedback metadata/comment turns",
|
|
375
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",
|
|
376
377
|
" --actor-scope <id> Actor workspace key inside mission-ops.yml (defaults to machine-cwd slug)",
|
|
377
378
|
" --wait Poll batch status until terminal completion for run-qaqc",
|
|
378
379
|
" --sync After run-qaqc completes, refresh local QAQC file",
|
|
@@ -392,6 +393,8 @@ function printHelp() {
|
|
|
392
393
|
" myte suggestions review --file ./review.yml",
|
|
393
394
|
" myte run-qaqc --mission-ids \"M001,M002\" --wait --sync",
|
|
394
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",
|
|
395
398
|
" myte bootstrap --output-dir ./MyteCommandCenter",
|
|
396
399
|
" myte sync-qaqc",
|
|
397
400
|
" myte update-team \"Backend deploy completed; QAQC rerun queued.\"",
|
|
@@ -614,8 +617,7 @@ function sleep(ms) {
|
|
|
614
617
|
|
|
615
618
|
async function getFetch() {
|
|
616
619
|
if (typeof fetch !== "undefined") return fetch;
|
|
617
|
-
|
|
618
|
-
return mod.default;
|
|
620
|
+
throw new Error("Global fetch is unavailable. myte requires Node 18+.");
|
|
619
621
|
}
|
|
620
622
|
|
|
621
623
|
function normalizeApiBase(baseRaw) {
|
|
@@ -2081,6 +2083,34 @@ async function createMissionStatusUpdate({ apiBase, key, timeoutMs, payload, ide
|
|
|
2081
2083
|
return body.data || {};
|
|
2082
2084
|
}
|
|
2083
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
|
+
|
|
2084
2114
|
async function fetchRunQaqcBatchStatus({ apiBase, key, timeoutMs, batchId }) {
|
|
2085
2115
|
const fetchFn = await getFetch();
|
|
2086
2116
|
const url = `${apiBase}/project-assistant/run-qaqc/${encodeURIComponent(String(batchId || ""))}`;
|
|
@@ -2415,6 +2445,109 @@ async function runMissionStatus(args) {
|
|
|
2415
2445
|
}
|
|
2416
2446
|
}
|
|
2417
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
|
+
|
|
2418
2551
|
function formatTargetContacts(contacts) {
|
|
2419
2552
|
const items = Array.isArray(contacts) ? contacts : [];
|
|
2420
2553
|
const formatted = items
|
|
@@ -2700,9 +2833,7 @@ function bootstrapScopedPathId(item, preferredKeys, scopeKeys, fallbackKeys, fal
|
|
|
2700
2833
|
}
|
|
2701
2834
|
|
|
2702
2835
|
function stringifyYaml(value) {
|
|
2703
|
-
|
|
2704
|
-
const YAML = require("yaml");
|
|
2705
|
-
return YAML.stringify(value, { lineWidth: 0 });
|
|
2836
|
+
return `${JSON.stringify(value, null, 2)}\n`;
|
|
2706
2837
|
}
|
|
2707
2838
|
|
|
2708
2839
|
function stableJsonStringify(value) {
|
|
@@ -2719,9 +2850,116 @@ function stableJsonStringify(value) {
|
|
|
2719
2850
|
}
|
|
2720
2851
|
|
|
2721
2852
|
function parseYaml(text) {
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
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;
|
|
2725
2963
|
}
|
|
2726
2964
|
|
|
2727
2965
|
function writeYamlFile(filePath, value) {
|
|
@@ -3579,6 +3817,34 @@ function normalizeItemsPayload(rawPayload, label) {
|
|
|
3579
3817
|
throw new Error(`${label} payload must be an object, array, or { items: [...] } envelope.`);
|
|
3580
3818
|
}
|
|
3581
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
|
+
|
|
3582
3848
|
function filterWorkspaceDraftsByIndex(drafts, indexesToRemove) {
|
|
3583
3849
|
const removal = new Set(Array.from(indexesToRemove || []).map((value) => Number(value)).filter((value) => Number.isInteger(value) && value > 0));
|
|
3584
3850
|
if (!Array.isArray(drafts) || !removal.size) {
|
|
@@ -4192,6 +4458,8 @@ async function runBootstrap(args) {
|
|
|
4192
4458
|
const wrapperRoot = resolved.root;
|
|
4193
4459
|
const outputDir = args["output-dir"] || args.outputDir || args.output_dir;
|
|
4194
4460
|
const dryRun = Boolean(args["dry-run"] || args.dryRun);
|
|
4461
|
+
const includeSuggestions = resolveBooleanFlag(args, "suggestions", true);
|
|
4462
|
+
const actorScope = resolveActorScope(args, {});
|
|
4195
4463
|
const summary = {
|
|
4196
4464
|
api_base: apiBase,
|
|
4197
4465
|
project_id: snapshot?.project?.id || null,
|
|
@@ -4212,6 +4480,16 @@ async function runBootstrap(args) {
|
|
|
4212
4480
|
snapshot_hash: snapshot.snapshot_hash || null,
|
|
4213
4481
|
generated_at: snapshot.generated_at || null,
|
|
4214
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
|
+
},
|
|
4215
4493
|
};
|
|
4216
4494
|
|
|
4217
4495
|
if (dryRun) {
|
|
@@ -4233,6 +4511,27 @@ async function runBootstrap(args) {
|
|
|
4233
4511
|
const writeResult = writeBootstrapSnapshot({ snapshot, wrapperRoot, outputDir });
|
|
4234
4512
|
summary.data_root = writeResult.dataRoot;
|
|
4235
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
|
+
|
|
4236
4535
|
if (args.json) {
|
|
4237
4536
|
console.log(JSON.stringify(summary, null, 2));
|
|
4238
4537
|
return;
|
|
@@ -4245,6 +4544,11 @@ async function runBootstrap(args) {
|
|
|
4245
4544
|
console.log(`Found locally: ${summary.local.found.join(", ") || "(none)"}`);
|
|
4246
4545
|
if (summary.local.missing.length) console.log(`Missing locally: ${summary.local.missing.join(", ")}`);
|
|
4247
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
|
+
}
|
|
4248
4552
|
console.log(`Snapshot: ${summary.snapshot_hash || "n/a"}`);
|
|
4249
4553
|
}
|
|
4250
4554
|
|
|
@@ -5293,6 +5597,11 @@ async function buildSuggestionsMutationContext(args, mode) {
|
|
|
5293
5597
|
if (clientSessionId && !payload.client_session_id) {
|
|
5294
5598
|
payload.client_session_id = clientSessionId;
|
|
5295
5599
|
}
|
|
5600
|
+
if (mode === "create") {
|
|
5601
|
+
validateSuggestionsCreatePayload(payload);
|
|
5602
|
+
} else if (mode === "revise") {
|
|
5603
|
+
validateSuggestionsRevisePayload(payload);
|
|
5604
|
+
}
|
|
5296
5605
|
const idempotencyKey = resolveSuggestionsIdempotencyKey({
|
|
5297
5606
|
args,
|
|
5298
5607
|
mode,
|
|
@@ -5898,7 +6207,7 @@ async function main() {
|
|
|
5898
6207
|
process.exit(1);
|
|
5899
6208
|
}
|
|
5900
6209
|
if (command === "mission") {
|
|
5901
|
-
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`.");
|
|
5902
6211
|
process.exit(1);
|
|
5903
6212
|
}
|
|
5904
6213
|
const args = parseArgs(rest);
|
|
@@ -5932,6 +6241,16 @@ async function main() {
|
|
|
5932
6241
|
return;
|
|
5933
6242
|
}
|
|
5934
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
|
+
|
|
5935
6254
|
if (command === "sync-qaqc" || command === "qaqc-sync") {
|
|
5936
6255
|
await runSyncQaqc(args);
|
|
5937
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
|
}
|