@mytegroupinc/myte-core 0.0.49 → 0.0.50
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 +62 -14
- package/cli.js +211 -51
- package/package.json +2 -2
- package/scripts/feedback-live-full-harness.js +432 -26
- package/scripts/project-assistant-read-certification.js +213 -1
package/README.md
CHANGED
|
@@ -38,6 +38,12 @@ This package exists so the public wrapper can stay small and versioned cleanly.
|
|
|
38
38
|
inserts cannot shift later pages. It remains compatible with older
|
|
39
39
|
offset-only API versions and rejects duplicate cross-page records instead of
|
|
40
40
|
committing an ambiguous local snapshot.
|
|
41
|
+
- `feedback-sync` keeps stable comment IDs and nests every comment attachment
|
|
42
|
+
under its originating comment. Readable text, Markdown, and DOCX attachment
|
|
43
|
+
context is materialized under
|
|
44
|
+
`MyteCommandCenter/feedback-sync/comment-attachments`; binary formats remain
|
|
45
|
+
visible as metadata-only context. Raw S3/object-storage URLs are not written
|
|
46
|
+
into local sync artifacts.
|
|
41
47
|
- `feedback status|edit|assign|archive|refine` writes reviewable local YAML artifacts.
|
|
42
48
|
- `feedback submit|revise|comment` are live writes that require `--confirm-write --approval-artifact <path>`.
|
|
43
49
|
- `feedback review|move|undo|apply` are governed writes with backend permission checks but no approval artifact flags.
|
|
@@ -145,12 +151,28 @@ their values and does not disable TLS verification. Captive portals and
|
|
|
145
151
|
intercepting gateways are reported as non-JSON/HTML transport failures rather
|
|
146
152
|
than as malformed Myte API responses.
|
|
147
153
|
|
|
148
|
-
Feedback comment support:
|
|
149
|
-
|
|
150
|
-
- `feedback-sync` includes existing feedback
|
|
151
|
-
|
|
154
|
+
Feedback comment support:
|
|
155
|
+
|
|
156
|
+
- `feedback-sync` includes existing feedback comments with `comment_id`,
|
|
157
|
+
sender, content, timestamps, and attachments nested under the originating
|
|
158
|
+
comment.
|
|
159
|
+
- Readable text, Markdown, and DOCX comment attachments are extracted by the
|
|
160
|
+
backend under attachment safety limits and written to
|
|
161
|
+
`MyteCommandCenter/feedback-sync/comment-attachments/<feedback>/<comment>/`.
|
|
162
|
+
The local YAML points to the file. Unsupported binary formats remain listed
|
|
163
|
+
with `context_status: metadata_only`. Extraction is capped at 20 readable
|
|
164
|
+
comment attachments per API page by default; additional readable files stay
|
|
165
|
+
linked to their comments with `context_status: deferred`. Use
|
|
166
|
+
`--comment-attachment-limit <0-100>` to change that bounded limit.
|
|
167
|
+
- `--no-with-prd-text` disables readable PRD and comment-attachment extraction
|
|
168
|
+
while retaining comment and attachment metadata.
|
|
169
|
+
- Feedback-specific comment creation exists in the web backend at `/api/feedbacks/<feedback_id>/comments`, protected by JWT/project assignment.
|
|
152
170
|
- Project-key comment creation is available through `myte feedback comment --feedback-id <id> --body "..." --confirm-write --approval-artifact ./comment.md` or `--body-file ./comment.md --confirm-write --approval-artifact ./comment.md`.
|
|
153
|
-
- The project-key feedback comment endpoint is text-only in this slice and caps content at 500,000 characters. Use the web UI when comment attachments are required.
|
|
171
|
+
- The project-key feedback comment endpoint is text-only in this slice and caps content at 500,000 characters. Use the web UI when comment attachments are required.
|
|
172
|
+
- `feedback apply --force` is an owner/elevated emergency override for a stale
|
|
173
|
+
local artifact. It requires the artifact to contain a non-empty reason and
|
|
174
|
+
returns the persisted force/warning audit context; normal edits should use
|
|
175
|
+
submit followed by owner/elevated review.
|
|
154
176
|
|
|
155
177
|
## Mission Action Map
|
|
156
178
|
|
|
@@ -241,16 +263,42 @@ Agent rule: never guess `suggestion_id`. If `mission-ops.yml` is missing or stal
|
|
|
241
263
|
| `feedback history` | Lists audited feedback board/refinement events. | Read-only; backend checks project access. |
|
|
242
264
|
| `feedback validate` | Sends an artifact to backend validation without mutation. | Useful before submit or owner-direct apply. |
|
|
243
265
|
| `feedback apply` | Applies an artifact through the owner-direct/emergency path. | No approval artifact required; not the normal collaborator flow; normal flow is `submit` -> `review`. |
|
|
266
|
+
|
|
267
|
+
Canonical feedback lifecycle states are `frozen`, `todo`, `in_progress`, `in_review`, `completed`, `deployed`, `rejected`, and `archived`.
|
|
268
|
+
|
|
269
|
+
### Feedback Permission Matrix
|
|
270
|
+
|
|
271
|
+
| Project role | Feedback permissions |
|
|
272
|
+
| --- | --- |
|
|
273
|
+
| Project Owner | Full Feedback lifecycle: view/sync/comment, draft/validate/submit, direct apply, approve/request changes/reject/cancel reviews, assignment, governed board states, archive, version history, and diffs. |
|
|
274
|
+
| Elevated collaborator (`owner_delegate`) | Same operational Feedback authority as the Project Owner, including direct apply, approval, assignment, governed board states, and archive. Project-level privilege grant/revoke and unrelated destructive infrastructure operations remain real-owner controls. |
|
|
275
|
+
| Builder collaborator | View/sync/comment, create and validate local edits, submit/revise/cancel their review requests, self-assign, and move active work through `todo`, `in_progress`, `in_review`, and `completed`. Cannot approve, directly apply, unassign others, archive, reject, freeze, or deploy Feedback. |
|
|
276
|
+
| Client collaborator | View/sync and participate in Feedback discussion. Does not receive builder edit, submit, assignment, or board-operation capabilities. |
|
|
277
|
+
| Organisation owner not assigned to the project | View access only; no project Feedback mutation authority. |
|
|
278
|
+
|
|
279
|
+
Stale local artifacts fail validation/apply with `snapshot_mismatch`. Re-sync,
|
|
280
|
+
review the newer server state, and create a fresh artifact; `force` is not part
|
|
281
|
+
of the normal collaborator workflow.
|
|
244
282
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
283
|
+
Reusable live smoke harness after deploy:
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
node ./scripts/feedback-live-full-harness.js --confirm-live --base-url https://api.myte.dev/api
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
For strict owner/elevated/builder permission certification, set distinct
|
|
290
|
+
`MYTE_OWNER_API_KEY`, `MYTE_DELEGATE_API_KEY`, and
|
|
291
|
+
`MYTE_COLLABORATOR_API_KEY` values scoped to the same test project and add
|
|
292
|
+
`--require-role-matrix`. To certify a real web-created comment attachment,
|
|
293
|
+
provide an ephemeral `MYTE_WEB_ACCESS_TOKEN` for an assigned test user and add
|
|
294
|
+
`--require-comment-attachment-sync`.
|
|
295
|
+
|
|
296
|
+
The harness creates five disposable `TEST` feedback items plus one
|
|
297
|
+
three-document Feedback item. It verifies comment sync, optional comment
|
|
298
|
+
attachment materialization, stale-artifact rejection and fresh re-sync,
|
|
299
|
+
request-changes/revise, owner and elevated approvals, regular collaborator
|
|
300
|
+
denials, batch review and board movement, per-document refinement/version diff,
|
|
301
|
+
exact-ID archival, archived sync exclusion, and project-key unarchive denial.
|
|
254
302
|
|
|
255
303
|
## Direct Project API Surface
|
|
256
304
|
|
package/cli.js
CHANGED
|
@@ -135,8 +135,8 @@ function splitCommand(argv) {
|
|
|
135
135
|
return { command: "query", rest: argv };
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
function parseArgs(argv) {
|
|
139
|
-
const parsed = { _: []
|
|
138
|
+
function parseArgs(argv) {
|
|
139
|
+
const parsed = { _: [] };
|
|
140
140
|
const aliases = { q: "query", d: "with-diff", c: "context", h: "help" };
|
|
141
141
|
const setValue = (key, value) => {
|
|
142
142
|
const finalKey = aliases[key] || key;
|
|
@@ -328,12 +328,14 @@ function printHelp() {
|
|
|
328
328
|
" - Optional: --target-contact-id <id> (repeatable) or --target-contact-ids <id1,id2>",
|
|
329
329
|
" - If the project has no linked client contacts, the backend falls back to the project owner for internal projects",
|
|
330
330
|
"",
|
|
331
|
-
"feedback-sync contract:",
|
|
331
|
+
"feedback-sync contract:",
|
|
332
332
|
" - Runs from any workspace where you want local MyteCommandCenter data written",
|
|
333
333
|
" - Syncs non-archived feedback by default so local Command Center data stays focused on active visible work",
|
|
334
334
|
" - Archived feedback is intentionally excluded from normal sync; returning it to active work is web UI only",
|
|
335
|
-
" - Writes project feedback metadata and
|
|
336
|
-
" - Stores full PRD context in MyteCommandCenter/PRD/feedback-sync/*.md and points to those files from feedback.yml",
|
|
335
|
+
" - Writes project feedback metadata and comment turns, stable comment ids, and nested attachment metadata into MyteCommandCenter/data/feedback.yml",
|
|
336
|
+
" - Stores full PRD context in MyteCommandCenter/PRD/feedback-sync/*.md and points to those files from feedback.yml",
|
|
337
|
+
" - Stores readable text/Markdown/DOCX comment attachment context under MyteCommandCenter/feedback-sync/comment-attachments and never writes raw storage URLs",
|
|
338
|
+
" - Non-readable comment attachments remain nested under the correct comment as metadata_only context",
|
|
337
339
|
" - Reads existing feedback comments and can create text-only feedback-specific comments through the project-key API",
|
|
338
340
|
"",
|
|
339
341
|
"feedback review contract:",
|
|
@@ -428,7 +430,8 @@ function printHelp() {
|
|
|
428
430
|
" --due-date <date> Feedback due date target for edit artifacts",
|
|
429
431
|
" --review-note <text> Review note stored in feedback refinement history",
|
|
430
432
|
" --with-prd-text Include extracted PRD text so local PRD files are materialized during feedback-sync (default: on)",
|
|
431
|
-
" --no-with-prd-text Skip PRD
|
|
433
|
+
" --no-with-prd-text Skip PRD and readable comment-attachment extraction; keep feedback/comment/attachment metadata",
|
|
434
|
+
" --comment-attachment-limit <n> Cap readable comment-attachment extraction per sync page (0-100; default: 20)",
|
|
432
435
|
" --mission-ids <ids> Comma-separated mission business ids for run-qaqc, mission status, or mission archive (quote multi-id values on PowerShell)",
|
|
433
436
|
" --reason <text> Optional reason for governed mission archive or feedback changes",
|
|
434
437
|
" --actor-scope <id> Actor workspace key inside mission-ops.yml (defaults to machine-cwd slug)",
|
|
@@ -436,7 +439,7 @@ function printHelp() {
|
|
|
436
439
|
" --approval-artifact Local .md/.markdown/.yml/.yaml/.json approval artifact for gated write commands",
|
|
437
440
|
" --wait Poll batch status until terminal completion for run-qaqc",
|
|
438
441
|
" --sync After run-qaqc completes, refresh local QAQC file",
|
|
439
|
-
" --force Allow
|
|
442
|
+
" --force Allow QAQC retry or owner/elevated stale Feedback apply; Feedback force requires a reason",
|
|
440
443
|
" --no-sync Skip automatic post-mutation sync for suggestions, missions, and feedback mutations",
|
|
441
444
|
" --print-context Print JSON payload and exit (no query call)",
|
|
442
445
|
" --no-fetch Don't git fetch origin main/master before diff",
|
|
@@ -1879,18 +1882,18 @@ function collectGitDiffWithDiagnostics({
|
|
|
1879
1882
|
|
|
1880
1883
|
const headingParts = [`${name} (${dir || "."})`];
|
|
1881
1884
|
if (repo.role) headingParts.push(`role=${repo.role}`);
|
|
1882
|
-
let section = `### ${headingParts.join(" | ")}\n\n`;
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
}
|
|
1890
|
-
if (
|
|
1891
|
-
if (
|
|
1892
|
-
|
|
1893
|
-
if (
|
|
1885
|
+
let section = `### ${headingParts.join(" | ")}\n\n`;
|
|
1886
|
+
if (repoSummary.matched_by?.length) {
|
|
1887
|
+
section += `# ?? Matched by ${repoSummary.matched_by.join(", ")}\n`;
|
|
1888
|
+
}
|
|
1889
|
+
if (repoSummary.compare_remote_url) {
|
|
1890
|
+
section += `# ?? Compare remote ${repoSummary.compare_remote_url}\n`;
|
|
1891
|
+
}
|
|
1892
|
+
if (staged) section += `# ?? Staged changes\n${staged}\n\n`;
|
|
1893
|
+
if (unstaged) section += `# ?? Unstaged changes\n${unstaged}\n\n`;
|
|
1894
|
+
if (untrackedDiffs) section += `# ?? Untracked files (full contents below)\n${untrackedDiffs}\n`;
|
|
1895
|
+
section += `# ?? Base vs HEAD (${repoSummary.base_ref_label}...HEAD | head=${headBranch})\n`;
|
|
1896
|
+
if (baseDiff) section += `${baseDiff}\n\n`;
|
|
1894
1897
|
|
|
1895
1898
|
if (!repoSummary.has_changes) {
|
|
1896
1899
|
section += "_No local changes - working tree clean_\n\n";
|
|
@@ -2008,6 +2011,21 @@ async function fetchFeedbackSyncSnapshot({ apiBase, key, timeoutMs, filters = {}
|
|
|
2008
2011
|
if (filters.includeCommentTurns !== undefined) {
|
|
2009
2012
|
url.searchParams.set("include_comment_turns", filters.includeCommentTurns ? "true" : "false");
|
|
2010
2013
|
}
|
|
2014
|
+
if (filters.includeCommentAttachmentText !== undefined) {
|
|
2015
|
+
url.searchParams.set(
|
|
2016
|
+
"include_comment_attachment_text",
|
|
2017
|
+
filters.includeCommentAttachmentText ? "true" : "false"
|
|
2018
|
+
);
|
|
2019
|
+
}
|
|
2020
|
+
if (
|
|
2021
|
+
filters.commentAttachmentTextLimit !== undefined
|
|
2022
|
+
&& filters.commentAttachmentTextLimit !== null
|
|
2023
|
+
) {
|
|
2024
|
+
url.searchParams.set(
|
|
2025
|
+
"comment_attachment_text_limit",
|
|
2026
|
+
String(filters.commentAttachmentTextLimit)
|
|
2027
|
+
);
|
|
2028
|
+
}
|
|
2011
2029
|
if (filters.limit !== undefined && filters.limit !== null && filters.limit !== "") {
|
|
2012
2030
|
url.searchParams.set("limit", String(filters.limit));
|
|
2013
2031
|
}
|
|
@@ -2053,6 +2071,9 @@ function aggregateFeedbackSyncCounts(items, availableTotal) {
|
|
|
2053
2071
|
const bySource = {};
|
|
2054
2072
|
let withPrdText = 0;
|
|
2055
2073
|
let withConversationTurns = 0;
|
|
2074
|
+
let withCommentAttachments = 0;
|
|
2075
|
+
let commentAttachments = 0;
|
|
2076
|
+
const commentAttachmentContext = {};
|
|
2056
2077
|
for (const item of items) {
|
|
2057
2078
|
const status = String(item?.status || "Pending");
|
|
2058
2079
|
const source = String(item?.source || "User");
|
|
@@ -2062,12 +2083,30 @@ function aggregateFeedbackSyncCounts(items, availableTotal) {
|
|
|
2062
2083
|
if (Array.isArray(item?.conversation_turns) && item.conversation_turns.length > 0) {
|
|
2063
2084
|
withConversationTurns += 1;
|
|
2064
2085
|
}
|
|
2086
|
+
const itemCommentAttachments = (item?.conversation_turns || []).reduce(
|
|
2087
|
+
(total, turn) => total + (Array.isArray(turn?.attachments) ? turn.attachments.length : 0),
|
|
2088
|
+
0
|
|
2089
|
+
);
|
|
2090
|
+
if (itemCommentAttachments > 0) {
|
|
2091
|
+
withCommentAttachments += 1;
|
|
2092
|
+
commentAttachments += itemCommentAttachments;
|
|
2093
|
+
for (const turn of item?.conversation_turns || []) {
|
|
2094
|
+
for (const attachment of turn?.attachments || []) {
|
|
2095
|
+
const contextStatus = String(attachment?.context_status || "metadata_only");
|
|
2096
|
+
commentAttachmentContext[contextStatus] =
|
|
2097
|
+
(commentAttachmentContext[contextStatus] || 0) + 1;
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2065
2101
|
}
|
|
2066
2102
|
return {
|
|
2067
2103
|
total_feedback: items.length,
|
|
2068
2104
|
available_feedback: Number.isFinite(Number(availableTotal)) ? Number(availableTotal) : items.length,
|
|
2069
2105
|
with_prd_text: withPrdText,
|
|
2070
2106
|
with_conversation_turns: withConversationTurns,
|
|
2107
|
+
with_comment_attachments: withCommentAttachments,
|
|
2108
|
+
comment_attachments: commentAttachments,
|
|
2109
|
+
comment_attachment_context: commentAttachmentContext,
|
|
2071
2110
|
by_status: byStatus,
|
|
2072
2111
|
by_source: bySource,
|
|
2073
2112
|
};
|
|
@@ -3959,22 +3998,47 @@ function ensureTrailingNewline(value) {
|
|
|
3959
3998
|
return text.endsWith("\n") ? text : `${text}\n`;
|
|
3960
3999
|
}
|
|
3961
4000
|
|
|
3962
|
-
function normalizeFeedbackConversationTurns(turns) {
|
|
3963
|
-
if (!Array.isArray(turns)) return [];
|
|
3964
|
-
return turns
|
|
3965
|
-
.map((turn) => {
|
|
3966
|
-
const content = String(turn?.content || "").trim();
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
}
|
|
4001
|
+
function normalizeFeedbackConversationTurns(turns) {
|
|
4002
|
+
if (!Array.isArray(turns)) return [];
|
|
4003
|
+
return turns
|
|
4004
|
+
.map((turn) => {
|
|
4005
|
+
const content = String(turn?.content || "").trim();
|
|
4006
|
+
const attachments = (Array.isArray(turn?.attachments) ? turn.attachments : [])
|
|
4007
|
+
.map((attachment) => {
|
|
4008
|
+
if (!isPlainObject(attachment)) return null;
|
|
4009
|
+
const normalizedAttachment = {
|
|
4010
|
+
name: firstNonEmptyString(attachment.name) || "attachment",
|
|
4011
|
+
context_status: firstNonEmptyString(attachment.context_status) || "metadata_only",
|
|
4012
|
+
};
|
|
4013
|
+
for (const field of ["type", "asset_kind", "download_name", "format"]) {
|
|
4014
|
+
const value = firstNonEmptyString(attachment[field]);
|
|
4015
|
+
if (value) normalizedAttachment[field] = value;
|
|
4016
|
+
}
|
|
4017
|
+
const size = Number(attachment.size);
|
|
4018
|
+
if (Number.isFinite(size) && size >= 0) normalizedAttachment.size = size;
|
|
4019
|
+
const attachmentContent = String(attachment.content || "");
|
|
4020
|
+
if (attachmentContent.trim()) normalizedAttachment.content = attachmentContent;
|
|
4021
|
+
if (attachment.truncated !== undefined) {
|
|
4022
|
+
normalizedAttachment.truncated = Boolean(attachment.truncated);
|
|
4023
|
+
}
|
|
4024
|
+
return normalizedAttachment;
|
|
4025
|
+
})
|
|
4026
|
+
.filter(Boolean);
|
|
4027
|
+
if (!content && attachments.length === 0) return null;
|
|
4028
|
+
const normalized = {
|
|
4029
|
+
comment_id: firstNonEmptyString(turn?.comment_id, turn?.id),
|
|
4030
|
+
sender_name: firstNonEmptyString(turn?.sender_name, turn?.user_name, turn?.author_name) || "Unknown",
|
|
4031
|
+
content,
|
|
4032
|
+
attachments,
|
|
4033
|
+
};
|
|
4034
|
+
const createdAt = firstNonEmptyString(turn?.created_at, turn?.timestamp);
|
|
4035
|
+
if (createdAt) normalized.created_at = createdAt;
|
|
4036
|
+
const updatedAt = firstNonEmptyString(turn?.updated_at);
|
|
4037
|
+
if (updatedAt) normalized.updated_at = updatedAt;
|
|
4038
|
+
return normalized;
|
|
4039
|
+
})
|
|
4040
|
+
.filter(Boolean);
|
|
4041
|
+
}
|
|
3978
4042
|
|
|
3979
4043
|
function readJsonFile(filePath) {
|
|
3980
4044
|
if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) return null;
|
|
@@ -4336,20 +4400,83 @@ function writeQaqcSnapshot({ snapshot, wrapperRoot, outputDir }) {
|
|
|
4336
4400
|
function writeFeedbackSnapshot({ snapshot, wrapperRoot, outputDir }) {
|
|
4337
4401
|
const { targetRoot, dataRoot } = resolveCommandCenterRoots(wrapperRoot, outputDir);
|
|
4338
4402
|
const prdSyncDir = path.join(targetRoot, "PRD", "feedback-sync");
|
|
4403
|
+
const commentAttachmentsDir = path.join(targetRoot, "feedback-sync", "comment-attachments");
|
|
4339
4404
|
const transactionToken = `${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
4340
4405
|
const stagingRoot = path.join(targetRoot, `.myte-feedback-sync-${transactionToken}`);
|
|
4341
4406
|
const stagedDataRoot = path.join(stagingRoot, "data");
|
|
4342
4407
|
const stagedPrdRoot = path.join(stagingRoot, "PRD", "feedback-sync");
|
|
4408
|
+
const stagedCommentAttachmentsRoot = path.join(stagingRoot, "feedback-sync", "comment-attachments");
|
|
4343
4409
|
ensureDir(stagedDataRoot);
|
|
4344
4410
|
ensureDir(stagedPrdRoot);
|
|
4411
|
+
ensureDir(stagedCommentAttachmentsRoot);
|
|
4345
4412
|
|
|
4346
4413
|
const items = Array.isArray(snapshot.items) ? snapshot.items : [];
|
|
4347
4414
|
let prdFileCount = 0;
|
|
4348
4415
|
let prdDocumentSetCount = 0;
|
|
4416
|
+
let commentAttachmentCount = 0;
|
|
4417
|
+
let commentAttachmentFileCount = 0;
|
|
4418
|
+
const commentAttachmentContextCounts = {};
|
|
4349
4419
|
const materializedItems = items.map((rawItem, index) => {
|
|
4350
4420
|
const item = isPlainObject(rawItem) ? { ...rawItem } : {};
|
|
4351
4421
|
const feedbackId = stableItemId(item, ["feedback_id", "id"], `F${String(index + 1).padStart(3, "0")}`);
|
|
4352
|
-
const
|
|
4422
|
+
const feedbackFolderName = sanitizeFileSegment(feedbackId, `feedback-${index + 1}`);
|
|
4423
|
+
const conversationTurns = normalizeFeedbackConversationTurns(item.conversation_turns)
|
|
4424
|
+
.map((turn, turnIndex) => {
|
|
4425
|
+
const commentId = stableItemId(
|
|
4426
|
+
turn,
|
|
4427
|
+
["comment_id", "id"],
|
|
4428
|
+
`comment-${String(turnIndex + 1).padStart(3, "0")}`
|
|
4429
|
+
);
|
|
4430
|
+
const commentFolderName = sanitizeFileSegment(commentId, `comment-${turnIndex + 1}`);
|
|
4431
|
+
const attachments = (turn.attachments || []).map((rawAttachment, attachmentIndex) => {
|
|
4432
|
+
const attachment = isPlainObject(rawAttachment) ? { ...rawAttachment } : {};
|
|
4433
|
+
const content = String(attachment.content || "");
|
|
4434
|
+
let localFile = null;
|
|
4435
|
+
commentAttachmentCount += 1;
|
|
4436
|
+
const contextStatus = String(attachment.context_status || "metadata_only");
|
|
4437
|
+
commentAttachmentContextCounts[contextStatus] =
|
|
4438
|
+
(commentAttachmentContextCounts[contextStatus] || 0) + 1;
|
|
4439
|
+
if (content.trim()) {
|
|
4440
|
+
const sourceName = sanitizeFileSegment(
|
|
4441
|
+
attachment.name,
|
|
4442
|
+
`attachment-${attachmentIndex + 1}`
|
|
4443
|
+
);
|
|
4444
|
+
const sourceBase = path.parse(sourceName).name || `attachment-${attachmentIndex + 1}`;
|
|
4445
|
+
const extension = String(attachment.format || "").toLowerCase() === "markdown"
|
|
4446
|
+
? "md"
|
|
4447
|
+
: "txt";
|
|
4448
|
+
const filename = `${String(attachmentIndex + 1).padStart(2, "0")}-${sourceBase}.${extension}`;
|
|
4449
|
+
const stagedPath = path.join(
|
|
4450
|
+
stagedCommentAttachmentsRoot,
|
|
4451
|
+
feedbackFolderName,
|
|
4452
|
+
commentFolderName,
|
|
4453
|
+
filename
|
|
4454
|
+
);
|
|
4455
|
+
ensureDir(path.dirname(stagedPath));
|
|
4456
|
+
writeTextFile(stagedPath, ensureTrailingNewline(content));
|
|
4457
|
+
localFile = toPosixRelativePath(
|
|
4458
|
+
targetRoot,
|
|
4459
|
+
path.join(
|
|
4460
|
+
commentAttachmentsDir,
|
|
4461
|
+
feedbackFolderName,
|
|
4462
|
+
commentFolderName,
|
|
4463
|
+
filename
|
|
4464
|
+
)
|
|
4465
|
+
);
|
|
4466
|
+
commentAttachmentFileCount += 1;
|
|
4467
|
+
}
|
|
4468
|
+
delete attachment.content;
|
|
4469
|
+
return {
|
|
4470
|
+
...attachment,
|
|
4471
|
+
local_file: localFile,
|
|
4472
|
+
};
|
|
4473
|
+
});
|
|
4474
|
+
return {
|
|
4475
|
+
...turn,
|
|
4476
|
+
comment_id: commentId,
|
|
4477
|
+
attachments,
|
|
4478
|
+
};
|
|
4479
|
+
});
|
|
4353
4480
|
const prdText = String(item.prd_text || "").trim();
|
|
4354
4481
|
const rawPrdDocuments = Array.isArray(item.prd_documents) ? item.prd_documents : [];
|
|
4355
4482
|
const attachmentDocuments = Array.isArray(item.attachment_documents) ? item.attachment_documents : [];
|
|
@@ -4363,7 +4490,6 @@ function writeFeedbackSnapshot({ snapshot, wrapperRoot, outputDir }) {
|
|
|
4363
4490
|
|
|
4364
4491
|
if (rawPrdDocuments.length > 0) {
|
|
4365
4492
|
prdDocumentSetCount += 1;
|
|
4366
|
-
const feedbackFolderName = sanitizeFileSegment(feedbackId, `feedback-${index + 1}`);
|
|
4367
4493
|
materializedPrdDocuments = rawPrdDocuments.map((rawDocument, documentIndex) => {
|
|
4368
4494
|
const document = isPlainObject(rawDocument) ? { ...rawDocument } : {};
|
|
4369
4495
|
const markdown = String(
|
|
@@ -4439,9 +4565,17 @@ function writeFeedbackSnapshot({ snapshot, wrapperRoot, outputDir }) {
|
|
|
4439
4565
|
: { total_feedback: materializedItems.length };
|
|
4440
4566
|
snapshotCounts.with_prd_files = prdFileCount;
|
|
4441
4567
|
snapshotCounts.with_prd_document_sets = prdDocumentSetCount;
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4568
|
+
snapshotCounts.comment_attachments = commentAttachmentCount;
|
|
4569
|
+
snapshotCounts.comment_attachment_context = commentAttachmentContextCounts;
|
|
4570
|
+
snapshotCounts.readable_comment_attachment_files = commentAttachmentFileCount;
|
|
4571
|
+
if (snapshotCounts.with_conversation_turns === undefined) {
|
|
4572
|
+
snapshotCounts.with_conversation_turns = materializedItems.filter((item) => Array.isArray(item?.conversation_turns) && item.conversation_turns.length > 0).length;
|
|
4573
|
+
}
|
|
4574
|
+
snapshotCounts.with_comment_attachments = materializedItems.filter(
|
|
4575
|
+
(item) => (item?.conversation_turns || []).some(
|
|
4576
|
+
(turn) => Array.isArray(turn?.attachments) && turn.attachments.length > 0
|
|
4577
|
+
)
|
|
4578
|
+
).length;
|
|
4445
4579
|
|
|
4446
4580
|
const payload = scrubBootstrapValue({
|
|
4447
4581
|
schema_version: snapshot.schema_version || 2,
|
|
@@ -4451,8 +4585,11 @@ function writeFeedbackSnapshot({ snapshot, wrapperRoot, outputDir }) {
|
|
|
4451
4585
|
counts: snapshotCounts,
|
|
4452
4586
|
artifacts: {
|
|
4453
4587
|
feedback_prd_root: "PRD/feedback-sync",
|
|
4588
|
+
feedback_comment_attachment_root: "feedback-sync/comment-attachments",
|
|
4454
4589
|
with_prd_files: prdFileCount,
|
|
4455
4590
|
with_prd_document_sets: prdDocumentSetCount,
|
|
4591
|
+
comment_attachments: commentAttachmentCount,
|
|
4592
|
+
readable_comment_attachment_files: commentAttachmentFileCount,
|
|
4456
4593
|
},
|
|
4457
4594
|
queue: materializedItems
|
|
4458
4595
|
.filter((item) => String(item?.status || "").trim().toLowerCase() !== "resolved")
|
|
@@ -4494,6 +4631,10 @@ function writeFeedbackSnapshot({ snapshot, wrapperRoot, outputDir }) {
|
|
|
4494
4631
|
staged: stagedPrdRoot,
|
|
4495
4632
|
target: prdSyncDir,
|
|
4496
4633
|
},
|
|
4634
|
+
{
|
|
4635
|
+
staged: stagedCommentAttachmentsRoot,
|
|
4636
|
+
target: commentAttachmentsDir,
|
|
4637
|
+
},
|
|
4497
4638
|
{
|
|
4498
4639
|
staged: stagedFeedbackPath,
|
|
4499
4640
|
target: path.join(dataRoot, "feedback.yml"),
|
|
@@ -4505,6 +4646,7 @@ function writeFeedbackSnapshot({ snapshot, wrapperRoot, outputDir }) {
|
|
|
4505
4646
|
targetRoot,
|
|
4506
4647
|
dataRoot,
|
|
4507
4648
|
prdSyncDir,
|
|
4649
|
+
commentAttachmentsDir,
|
|
4508
4650
|
manifest: payload,
|
|
4509
4651
|
};
|
|
4510
4652
|
} finally {
|
|
@@ -4656,11 +4798,13 @@ function summarizeFeedbackRefinementResult(data) {
|
|
|
4656
4798
|
new_snapshot_hash: data?.new_snapshot_hash || data?.feedback?.snapshot_hash || null,
|
|
4657
4799
|
diff_count: Array.isArray(data?.diffs) ? data.diffs.length : 0,
|
|
4658
4800
|
diffs: data?.diffs || [],
|
|
4659
|
-
warnings: data?.warnings || [],
|
|
4660
|
-
errors: data?.errors || [],
|
|
4661
|
-
history_id: data?.history?.history_id || null,
|
|
4662
|
-
|
|
4663
|
-
|
|
4801
|
+
warnings: data?.warnings || [],
|
|
4802
|
+
errors: data?.errors || [],
|
|
4803
|
+
history_id: data?.history?.history_id || null,
|
|
4804
|
+
feedback: data?.feedback || null,
|
|
4805
|
+
history: data?.history || null,
|
|
4806
|
+
};
|
|
4807
|
+
}
|
|
4664
4808
|
|
|
4665
4809
|
function missionOpsThreads(payload) {
|
|
4666
4810
|
if (Array.isArray(payload?.threads)) return payload.threads;
|
|
@@ -6275,6 +6419,7 @@ async function runFeedbackSync(args) {
|
|
|
6275
6419
|
const includePrdText = resolveBooleanFlag(args, "with-prd-text", true);
|
|
6276
6420
|
let pageSize;
|
|
6277
6421
|
let maxItems;
|
|
6422
|
+
let commentAttachmentTextLimit;
|
|
6278
6423
|
try {
|
|
6279
6424
|
pageSize = resolveBoundedInteger(
|
|
6280
6425
|
args["page-size"] || args.pageSize || args.page_size,
|
|
@@ -6284,16 +6429,29 @@ async function runFeedbackSync(args) {
|
|
|
6284
6429
|
args.limit,
|
|
6285
6430
|
{ fallback: 0, min: 0, max: 100_000, label: "limit" }
|
|
6286
6431
|
);
|
|
6432
|
+
commentAttachmentTextLimit = resolveBoundedInteger(
|
|
6433
|
+
args["comment-attachment-limit"]
|
|
6434
|
+
|| args.commentAttachmentLimit
|
|
6435
|
+
|| args.comment_attachment_limit,
|
|
6436
|
+
{
|
|
6437
|
+
fallback: 20,
|
|
6438
|
+
min: 0,
|
|
6439
|
+
max: 100,
|
|
6440
|
+
label: "comment-attachment-limit",
|
|
6441
|
+
}
|
|
6442
|
+
);
|
|
6287
6443
|
} catch (err) {
|
|
6288
6444
|
console.error(err?.message || err);
|
|
6289
6445
|
process.exit(1);
|
|
6290
6446
|
}
|
|
6291
6447
|
const filters = {
|
|
6292
|
-
status: firstNonEmptyString(args.status) || "",
|
|
6293
|
-
source: firstNonEmptyString(args.source) || "",
|
|
6294
|
-
includePrdText,
|
|
6295
|
-
includeCommentTurns: true,
|
|
6296
|
-
|
|
6448
|
+
status: firstNonEmptyString(args.status) || "",
|
|
6449
|
+
source: firstNonEmptyString(args.source) || "",
|
|
6450
|
+
includePrdText,
|
|
6451
|
+
includeCommentTurns: true,
|
|
6452
|
+
includeCommentAttachmentText: includePrdText,
|
|
6453
|
+
commentAttachmentTextLimit,
|
|
6454
|
+
};
|
|
6297
6455
|
|
|
6298
6456
|
let snapshot;
|
|
6299
6457
|
try {
|
|
@@ -6403,8 +6561,10 @@ async function refreshFeedbackSnapshotAfterMutation(args, { apiBase, key, timeou
|
|
|
6403
6561
|
filters: {
|
|
6404
6562
|
status: "",
|
|
6405
6563
|
source: "",
|
|
6406
|
-
includePrdText: true,
|
|
6564
|
+
includePrdText: true,
|
|
6407
6565
|
includeCommentTurns: true,
|
|
6566
|
+
includeCommentAttachmentText: true,
|
|
6567
|
+
commentAttachmentTextLimit: 20,
|
|
6408
6568
|
},
|
|
6409
6569
|
pageSize: DEFAULT_FEEDBACK_SYNC_PAGE_SIZE,
|
|
6410
6570
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mytegroupinc/myte-core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.50",
|
|
4
4
|
"description": "Myte CLI core implementation.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "cli.js",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"package.json"
|
|
16
16
|
],
|
|
17
17
|
"scripts": {
|
|
18
|
-
"test": "node --test"
|
|
18
|
+
"test": "node --test --test-concurrency=1"
|
|
19
19
|
},
|
|
20
20
|
"license": "MIT",
|
|
21
21
|
"engines": {
|