@zq-silk/yui 0.6.14 → 0.6.16
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/dist/cli/commandCatalog.js +1 -7
- package/dist/cli.js +8 -5
- package/dist/commands/projectCommands.js +15 -54
- package/dist/context/sessionContextBudget.js +5 -2
- package/dist/controller/fileSchedulerStoreAdapter.js +8 -0
- package/dist/lifecycle/exactRunTerminalization.js +5 -1
- package/dist/review/deltaRecheck.js +31 -10
- package/dist/run/recoveryProjection.js +4 -5
- package/dist/runtime/runtimeObservation.js +6 -1
- package/dist/storage/migration/productionRegistry.js +15 -1
- package/dist/task/completionReadiness.js +13 -6
- package/package.json +1 -1
- package/skills/yui-operator/SKILL.md +6 -2
|
@@ -1137,7 +1137,7 @@ export const ROOT_COMMAND = buildNode({
|
|
|
1137
1137
|
{
|
|
1138
1138
|
id: "manage",
|
|
1139
1139
|
title: "Commands",
|
|
1140
|
-
entries: ["add", "
|
|
1140
|
+
entries: ["add", "retire", "list", "show", "propose", "proposals", "accept", "reject"]
|
|
1141
1141
|
}
|
|
1142
1142
|
],
|
|
1143
1143
|
children: [
|
|
@@ -1147,12 +1147,6 @@ export const ROOT_COMMAND = buildNode({
|
|
|
1147
1147
|
usage: "yui project knowledge add <project> <title> --body <text>",
|
|
1148
1148
|
options: ["--body"]
|
|
1149
1149
|
},
|
|
1150
|
-
{
|
|
1151
|
-
name: "update",
|
|
1152
|
-
summary: "Update active Project knowledge (Operator authority).",
|
|
1153
|
-
usage: "yui project knowledge update <project> <knowledge> [--title <text>] [--body <text>]",
|
|
1154
|
-
options: ["--title", "--body"]
|
|
1155
|
-
},
|
|
1156
1150
|
{
|
|
1157
1151
|
name: "retire",
|
|
1158
1152
|
summary: "Retire Project knowledge without deleting its record (Operator authority).",
|
package/dist/cli.js
CHANGED
|
@@ -1699,13 +1699,16 @@ async function deltaRecheckPreflightForTaskCommand(args, store, actualTaskReview
|
|
|
1699
1699
|
if (previous === undefined) {
|
|
1700
1700
|
throw usageError("Delta-recheck requires a previous completed Task-final Review that accepted a head.");
|
|
1701
1701
|
}
|
|
1702
|
-
const
|
|
1703
|
-
const
|
|
1704
|
-
|
|
1705
|
-
|
|
1702
|
+
const repositoryPaths = {};
|
|
1703
|
+
for (const candidateProject of actualTaskReviewCandidate.projects) {
|
|
1704
|
+
const project = store.getProject(candidateProject.projectId);
|
|
1705
|
+
if (project === null) {
|
|
1706
|
+
throw usageError(`Delta-recheck Project not found: ${candidateProject.projectId}.`);
|
|
1707
|
+
}
|
|
1708
|
+
repositoryPaths[candidateProject.projectId] = project.path;
|
|
1706
1709
|
}
|
|
1707
1710
|
const assessment = await assessDeltaRecheck({
|
|
1708
|
-
|
|
1711
|
+
repositoryPaths,
|
|
1709
1712
|
previousRound: previous,
|
|
1710
1713
|
candidate: actualTaskReviewCandidate,
|
|
1711
1714
|
git: new NodeGitWorkspace(),
|
|
@@ -610,23 +610,10 @@ function projectKnowledge(args, store, options) {
|
|
|
610
610
|
};
|
|
611
611
|
}
|
|
612
612
|
if (command === "update") {
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
const project = requireProject(tx, parsed.project);
|
|
618
|
-
const next = updateProjectKnowledge(project, parsed.id, {
|
|
619
|
-
...(parsed.title === undefined ? {} : { title: parsed.title }),
|
|
620
|
-
...(parsed.body === undefined ? {} : { body: parsed.body })
|
|
621
|
-
}, (options.now ?? (() => new Date()))());
|
|
622
|
-
tx.saveProject(next);
|
|
623
|
-
return next;
|
|
624
|
-
});
|
|
625
|
-
return {
|
|
626
|
-
output: `Updated project knowledge ${parsed.id} in ${updated.id}\n`,
|
|
627
|
-
projectId: updated.id,
|
|
628
|
-
knowledgeId: parsed.id
|
|
629
|
-
};
|
|
613
|
+
throw usageError("Direct Project Knowledge update is not supported because it would erase version history. "
|
|
614
|
+
+ "Propose the replacement with `yui project knowledge propose <project> ... --task <task>` "
|
|
615
|
+
+ "and let the Operator apply it with `yui project knowledge accept <project> <proposal> "
|
|
616
|
+
+ "--update <knowledge>`; use --supersedes when replacing Knowledge without proposal history.");
|
|
630
617
|
}
|
|
631
618
|
if (command === "retire") {
|
|
632
619
|
if (rest.length !== 2) {
|
|
@@ -1024,6 +1011,17 @@ function acceptKnowledgeProposal(args, store, options) {
|
|
|
1024
1011
|
if (target.status !== "active") {
|
|
1025
1012
|
throw usageError(`Knowledge is not active: ${target.id}.`);
|
|
1026
1013
|
}
|
|
1014
|
+
const previousProposal = target.provenance?.proposalId === undefined
|
|
1015
|
+
? null
|
|
1016
|
+
: findKnowledgeProposal(next, target.provenance.proposalId);
|
|
1017
|
+
if (previousProposal === null
|
|
1018
|
+
|| previousProposal.status !== "accepted"
|
|
1019
|
+
|| previousProposal.knowledgeId !== target.id
|
|
1020
|
+
|| previousProposal.title !== target.title
|
|
1021
|
+
|| previousProposal.body !== target.body) {
|
|
1022
|
+
throw usageError(`Knowledge ${target.id} has no complete proposal-backed version history. `
|
|
1023
|
+
+ "Create the replacement proposal with --supersedes instead of --update.");
|
|
1024
|
+
}
|
|
1027
1025
|
knowledgeId = target.id;
|
|
1028
1026
|
next = updateProjectKnowledge(next, knowledgeId, {
|
|
1029
1027
|
title: proposal.title,
|
|
@@ -1255,43 +1253,6 @@ function parseProjectUpdateArguments(args, usage) {
|
|
|
1255
1253
|
...(development === undefined ? {} : { development })
|
|
1256
1254
|
};
|
|
1257
1255
|
}
|
|
1258
|
-
function parseKnowledgeUpdateArguments(args, usage) {
|
|
1259
|
-
const positionals = [];
|
|
1260
|
-
let title;
|
|
1261
|
-
let body;
|
|
1262
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
1263
|
-
const value = args[index];
|
|
1264
|
-
if (value === "--title" || value === "--body") {
|
|
1265
|
-
const next = args[index + 1];
|
|
1266
|
-
if (next === undefined || next.startsWith("--"))
|
|
1267
|
-
throw usageError(`${value} is required. ${usage}`);
|
|
1268
|
-
if (value === "--title") {
|
|
1269
|
-
if (title !== undefined)
|
|
1270
|
-
throw usageError(`--title may only be provided once. ${usage}`);
|
|
1271
|
-
title = requireText(next, value);
|
|
1272
|
-
}
|
|
1273
|
-
else {
|
|
1274
|
-
if (body !== undefined)
|
|
1275
|
-
throw usageError(`--body may only be provided once. ${usage}`);
|
|
1276
|
-
body = requireText(next, value);
|
|
1277
|
-
}
|
|
1278
|
-
index += 1;
|
|
1279
|
-
continue;
|
|
1280
|
-
}
|
|
1281
|
-
if (value.startsWith("--"))
|
|
1282
|
-
throw usageError(`Unknown option: ${value}. ${usage}`);
|
|
1283
|
-
positionals.push(value);
|
|
1284
|
-
}
|
|
1285
|
-
if (positionals.length !== 2 || (title === undefined && body === undefined)) {
|
|
1286
|
-
throw usageError(usage);
|
|
1287
|
-
}
|
|
1288
|
-
return {
|
|
1289
|
-
project: positionals[0],
|
|
1290
|
-
id: positionals[1],
|
|
1291
|
-
...(title === undefined ? {} : { title }),
|
|
1292
|
-
...(body === undefined ? {} : { body })
|
|
1293
|
-
};
|
|
1294
|
-
}
|
|
1295
1256
|
function parseCloneArguments(args, usage) {
|
|
1296
1257
|
const positionals = [];
|
|
1297
1258
|
const aliases = [];
|
|
@@ -60,8 +60,11 @@ function usageTotal(usage) {
|
|
|
60
60
|
const inputTokens = integer(record.inputTokens);
|
|
61
61
|
if (inputTokens === null)
|
|
62
62
|
return null;
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
// RuntimeUsageSnapshot.inputTokens is the normalized processed input total.
|
|
64
|
+
// cachedInputTokens is an informational breakdown of that total, matching
|
|
65
|
+
// runtimeProjection and the Driver documentation; adding it again would
|
|
66
|
+
// fabricate context pressure and trigger premature Session rollover.
|
|
67
|
+
return inputTokens;
|
|
65
68
|
}
|
|
66
69
|
function integer(value) {
|
|
67
70
|
return Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
@@ -2068,6 +2068,14 @@ export class FileSchedulerStoreAdapter {
|
|
|
2068
2068
|
if (input.roleName !== "leader") {
|
|
2069
2069
|
enqueueWork(store, { kind: "role", taskId: input.taskId, roleName: "leader" }, "provider-policy-blocked", now, [{ type: "run", taskId: input.taskId, id: input.runId }]);
|
|
2070
2070
|
}
|
|
2071
|
+
else {
|
|
2072
|
+
const message = `Leader Run ${input.runId} is blocked by Provider policy: ${summary}`;
|
|
2073
|
+
store.saveOperatorNotification(createLeaderRecoveryNotification(input.taskId, message, now, store.getOperatorNotification(input.taskId)));
|
|
2074
|
+
enqueueWork(store, { kind: "operator" }, "leader-provider-policy-blocked", now, [
|
|
2075
|
+
{ type: "task", id: input.taskId },
|
|
2076
|
+
{ type: "run", taskId: input.taskId, id: input.runId }
|
|
2077
|
+
]);
|
|
2078
|
+
}
|
|
2071
2079
|
return { disposition: "applied", runId: input.runId };
|
|
2072
2080
|
}
|
|
2073
2081
|
recordProviderRetryClassified(store, input, errorClass, extra, now) {
|
|
@@ -507,7 +507,11 @@ function matchesRecoverySessionFence(store, input) {
|
|
|
507
507
|
return false;
|
|
508
508
|
if (session.agentId !== input.agentId || session.adapterId !== input.adapterId)
|
|
509
509
|
return false;
|
|
510
|
-
|
|
510
|
+
// A dead Session is precisely when replace-session/terminate recovery is
|
|
511
|
+
// needed. Preserve its exact identity as the CAS fence; only same-Session
|
|
512
|
+
// retry is invalid once the native process is stopped or broken.
|
|
513
|
+
if ((session.status === "stopped" || session.status === "broken")
|
|
514
|
+
&& input.action === "retry")
|
|
511
515
|
return false;
|
|
512
516
|
const sessionNativeSessionId = session.nativeSessionId;
|
|
513
517
|
if (sessionNativeSessionId === undefined) {
|
|
@@ -7,7 +7,7 @@ import { validateDeltaRecheckRecord } from "./reviewRound.js";
|
|
|
7
7
|
* Reviewer's explicit disposition.
|
|
8
8
|
*/
|
|
9
9
|
export async function assessDeltaRecheck(input) {
|
|
10
|
-
const {
|
|
10
|
+
const { repositoryPaths, previousRound, candidate, git, config } = input;
|
|
11
11
|
if (previousRound.status !== "completed"
|
|
12
12
|
|| (previousRound.scope ?? "work-item") !== "task") {
|
|
13
13
|
return {
|
|
@@ -28,6 +28,13 @@ export async function assessDeltaRecheck(input) {
|
|
|
28
28
|
let deletedLines = 0;
|
|
29
29
|
let anyChange = false;
|
|
30
30
|
for (const project of candidate.projects) {
|
|
31
|
+
const repositoryPath = repositoryPaths[project.projectId];
|
|
32
|
+
if (repositoryPath === undefined) {
|
|
33
|
+
return {
|
|
34
|
+
kind: "ineligible",
|
|
35
|
+
reason: `Delta recheck repository is unavailable for Project ${project.projectId}.`
|
|
36
|
+
};
|
|
37
|
+
}
|
|
31
38
|
const previousHead = previousByProject.get(project.projectId);
|
|
32
39
|
if (previousHead === undefined) {
|
|
33
40
|
return {
|
|
@@ -185,14 +192,19 @@ function digestDiff(diffByProject) {
|
|
|
185
192
|
* the dispatch context; only path-like entries drive the deterministic gate.
|
|
186
193
|
*/
|
|
187
194
|
function extractEvidencePaths(round) {
|
|
188
|
-
return
|
|
195
|
+
return previousEvidenceReferences(round)
|
|
189
196
|
.filter((entry) => isPathLike(entry));
|
|
190
197
|
}
|
|
191
|
-
/** All evidence references from
|
|
198
|
+
/** All path-like evidence references from Markdown or JSON Review reports. */
|
|
192
199
|
function previousEvidenceReferences(round) {
|
|
193
|
-
|
|
200
|
+
const report = round.report ?? "";
|
|
201
|
+
return [...new Set([
|
|
202
|
+
...extractDeclaredEvidence(report),
|
|
203
|
+
...extractEvidenceReferences(report)
|
|
204
|
+
])];
|
|
194
205
|
}
|
|
195
|
-
|
|
206
|
+
/** Preserve the free-form evidence array that older JSON reports exposed. */
|
|
207
|
+
function extractDeclaredEvidence(report) {
|
|
196
208
|
let parsed;
|
|
197
209
|
try {
|
|
198
210
|
parsed = JSON.parse(report);
|
|
@@ -205,9 +217,18 @@ function extractEvidenceFromReportJson(report) {
|
|
|
205
217
|
const evidence = parsed.evidence;
|
|
206
218
|
if (!Array.isArray(evidence))
|
|
207
219
|
return [];
|
|
208
|
-
return evidence
|
|
209
|
-
|
|
210
|
-
|
|
220
|
+
return evidence.flatMap((entry) => (typeof entry === "string" && entry.trim().length > 0 ? [entry.trim()] : []));
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Reviewer reports intentionally accept clear Markdown or JSON without a
|
|
224
|
+
* fixed schema. Extract conservative repo-relative path tokens from the full
|
|
225
|
+
* preserved report so evidence overlap cannot be bypassed by presentation
|
|
226
|
+
* format, nested JSON, Markdown links, backticks, or line-qualified paths.
|
|
227
|
+
*/
|
|
228
|
+
function extractEvidenceReferences(report) {
|
|
229
|
+
const references = report.match(/(?:\.{0,2}\/)?[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]+)+|[A-Za-z0-9_.-]+\.[A-Za-z0-9]{1,12}/gu) ?? [];
|
|
230
|
+
return [...new Set(references.map((entry) => entry.replace(/^\.\//u, "")))]
|
|
231
|
+
.filter((entry) => isPathLike(entry));
|
|
211
232
|
}
|
|
212
233
|
function isPathLike(value) {
|
|
213
234
|
if (value.length === 0 || value.length > 512)
|
|
@@ -223,8 +244,8 @@ function isPathLike(value) {
|
|
|
223
244
|
return value.includes("/") || /\.[A-Za-z0-9]{1,12}$/u.test(value);
|
|
224
245
|
}
|
|
225
246
|
function pathEvidenceMatches(evidencePath, changedFile) {
|
|
226
|
-
const normalizedEvidence = evidencePath.replace(/^\.\//u, "");
|
|
227
|
-
const normalizedFile = changedFile.replace(/^\.\//u, "");
|
|
247
|
+
const normalizedEvidence = evidencePath.replace(/^\.\//u, "").replace(/\/+$/u, "");
|
|
248
|
+
const normalizedFile = changedFile.replace(/^\.\//u, "").replace(/\/+$/u, "");
|
|
228
249
|
if (normalizedEvidence === normalizedFile)
|
|
229
250
|
return true;
|
|
230
251
|
// A directory-level evidence reference covers every file under it.
|
|
@@ -57,8 +57,11 @@ export function projectRunRecovery(facts) {
|
|
|
57
57
|
? ["accepted", "ambiguous"]
|
|
58
58
|
: ["rejected", "ambiguous"];
|
|
59
59
|
const blocked = recoveryBlocker(facts, session, canonicalProgressAt);
|
|
60
|
+
const supportedActions = session?.status === "stopped" || session?.status === "broken"
|
|
61
|
+
? RUN_RECOVERY_ACTIONS.filter((action) => action !== "retry")
|
|
62
|
+
: RUN_RECOVERY_ACTIONS;
|
|
60
63
|
const actions = blocked === null
|
|
61
|
-
?
|
|
64
|
+
? supportedActions.map((action) => buildActionPlan(facts, action, session, canonicalProgressAt))
|
|
62
65
|
: [];
|
|
63
66
|
const judgmentRequired = blocked === null && actions.some((plan) => plan.argv.includes(PROVIDER_ACCEPTANCE_PLACEHOLDER))
|
|
64
67
|
? "Provider acceptance is not durably determined for every action; pass --provider-acceptance explicitly."
|
|
@@ -121,10 +124,6 @@ function recoveryBlocker(facts, session, canonicalProgressAt) {
|
|
|
121
124
|
return "progress-unavailable";
|
|
122
125
|
if (session === null)
|
|
123
126
|
return "session-missing";
|
|
124
|
-
if (session.status === "stopped")
|
|
125
|
-
return "session-stopped";
|
|
126
|
-
if (session.status === "broken")
|
|
127
|
-
return "session-broken";
|
|
128
127
|
return null;
|
|
129
128
|
}
|
|
130
129
|
function buildActionPlan(facts, action, session, canonicalProgressAt) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { requireDriverId } from "./agentDriver.js";
|
|
2
3
|
export const RUNTIME_OBSERVATION_TASK_EVENT = "runtime.observation";
|
|
3
4
|
const KINDS = [
|
|
@@ -405,7 +406,11 @@ export function runtimeObservationSemanticKey(input) {
|
|
|
405
406
|
].join(":");
|
|
406
407
|
}
|
|
407
408
|
if (input.kind === "continuation.reported") {
|
|
408
|
-
|
|
409
|
+
const summary = input.payload?.summary?.trim();
|
|
410
|
+
const resultIdentity = summary === undefined || summary.length === 0
|
|
411
|
+
? input.payload?.reportId ?? "missing"
|
|
412
|
+
: `sha256:${createHash("sha256").update(summary).digest("hex")}`;
|
|
413
|
+
return ["continuation-report", ...continuationIdentity, resultIdentity]
|
|
409
414
|
.join(":");
|
|
410
415
|
}
|
|
411
416
|
if (input.kind === "continuation.started") {
|
|
@@ -984,6 +984,11 @@ const STORED_TASK_V16_FIELDS = [
|
|
|
984
984
|
"integrationQueue",
|
|
985
985
|
"durableJobs",
|
|
986
986
|
"jobCallerKeyHashes",
|
|
987
|
+
// The SQLite reverse reader reconstructs every physical family map even
|
|
988
|
+
// when the older manifest has not introduced that family yet. A v16
|
|
989
|
+
// aggregate may therefore contain an empty wakes map; preserve that exact
|
|
990
|
+
// repair shape, but reject records because taskWake has no v16 version.
|
|
991
|
+
"wakes",
|
|
987
992
|
// May already be present after the publicationReference introduction runs
|
|
988
993
|
// first; the 16->17 normalizer preserves the empty introduced map.
|
|
989
994
|
"publicationReferences",
|
|
@@ -1022,6 +1027,12 @@ function requireStoredTaskV16Shape(snapshot) {
|
|
|
1022
1027
|
if (unknown !== undefined) {
|
|
1023
1028
|
throw new Error(`Task aggregate ${taskId} has an unknown v16 field: ${unknown}.`);
|
|
1024
1029
|
}
|
|
1030
|
+
if (task.wakes !== undefined) {
|
|
1031
|
+
const wakes = asObject(task.wakes, `Task aggregate ${taskId} wakes`);
|
|
1032
|
+
if (Object.keys(wakes).length > 0) {
|
|
1033
|
+
throw new Error(`Task aggregate ${taskId} already has Task wakes before v17.`);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1025
1036
|
if (task.publicationReferences !== undefined) {
|
|
1026
1037
|
const references = asObject(task.publicationReferences, `Task aggregate ${taskId} publicationReferences`);
|
|
1027
1038
|
if (Object.keys(references).length > 0) {
|
|
@@ -1049,6 +1060,9 @@ function normalizeStoredTaskV16ToV17(snapshot) {
|
|
|
1049
1060
|
const existingReferences = task.publicationReferences === undefined
|
|
1050
1061
|
? {}
|
|
1051
1062
|
: asObject(task.publicationReferences, `Task aggregate ${taskId} publicationReferences`);
|
|
1063
|
+
const existingWakes = task.wakes === undefined
|
|
1064
|
+
? {}
|
|
1065
|
+
: asObject(task.wakes, `Task aggregate ${taskId} wakes`);
|
|
1052
1066
|
const marks = asObject(task.idHighWaterMarks, `Task id high-water marks ${taskId}`);
|
|
1053
1067
|
const existingMark = marks.publicationReference;
|
|
1054
1068
|
const existingWakeMark = marks.taskWake;
|
|
@@ -1061,7 +1075,7 @@ function normalizeStoredTaskV16ToV17(snapshot) {
|
|
|
1061
1075
|
taskWake: typeof existingWakeMark === "number" ? existingWakeMark : TASK_WAKE_FROM_VERSION
|
|
1062
1076
|
},
|
|
1063
1077
|
publicationReferences: existingReferences,
|
|
1064
|
-
wakes:
|
|
1078
|
+
wakes: existingWakes
|
|
1065
1079
|
};
|
|
1066
1080
|
}
|
|
1067
1081
|
return {
|
|
@@ -33,12 +33,19 @@ export function projectCompletionReadiness(facts, options = {}) {
|
|
|
33
33
|
: `wait for Reviewer Run on ${round.id} to finish`
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
|
-
// Issue 07:
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
// Issue 07: only the latest completed Task-final Review can define whether
|
|
37
|
+
// the current review lineage is accepted. Historical delta findings and
|
|
38
|
+
// escalations remain audit evidence, but a later completed full Review (or
|
|
39
|
+
// accepted delta) supersedes them instead of blocking completion forever.
|
|
40
|
+
const latestCompletedTaskReview = facts.reviewRounds
|
|
41
|
+
.filter((round) => ((round.scope ?? "work-item") === "task" && round.status === "completed"))
|
|
42
|
+
.slice()
|
|
43
|
+
.sort((left, right) => (left.createdAt.localeCompare(right.createdAt)
|
|
44
|
+
|| left.id.localeCompare(right.id, undefined, { numeric: true })))
|
|
45
|
+
.at(-1);
|
|
46
|
+
if (latestCompletedTaskReview !== undefined
|
|
47
|
+
&& deltaRecheckBlocksAcceptance(latestCompletedTaskReview)) {
|
|
48
|
+
const round = latestCompletedTaskReview;
|
|
42
49
|
const disposition = round.deltaRecheck.disposition;
|
|
43
50
|
blockers.push({
|
|
44
51
|
code: "delta-recheck-not-accepted",
|
package/package.json
CHANGED
|
@@ -142,6 +142,7 @@ yui project knowledge show <project> <knowledge-id>
|
|
|
142
142
|
yui project knowledge proposals list <project>
|
|
143
143
|
yui project knowledge proposals show <project> <proposal-id>
|
|
144
144
|
yui project knowledge accept <project> <proposal-id>
|
|
145
|
+
yui project knowledge accept <project> <proposal-id> --update <knowledge-id>
|
|
145
146
|
yui project knowledge reject <project> <proposal-id> --reason "<text>"
|
|
146
147
|
yui task create "<title>" \
|
|
147
148
|
--project <project-a> --project <project-b> \
|
|
@@ -155,8 +156,11 @@ Task/Decision/Milestone evidence) and the Operator reviews and accepts or
|
|
|
155
156
|
rejects them. Acceptance writes the Knowledge entry with its provenance; a
|
|
156
157
|
candidate that duplicates an existing entry is deduplicated, and one that
|
|
157
158
|
conflicts with an existing title fails closed so the Operator must choose an
|
|
158
|
-
explicit
|
|
159
|
-
|
|
159
|
+
explicit proposal-backed `accept --update`, supersede, or reject. Direct
|
|
160
|
+
Knowledge mutation is not supported because it would erase version history;
|
|
161
|
+
`--update` is allowed only when the current version is already traceable to an
|
|
162
|
+
accepted proposal. Otherwise submit the replacement with `--supersedes` so the
|
|
163
|
+
old Knowledge entry remains retired and readable. If discovery finds an
|
|
160
164
|
existing stable checkout, bind it with `project add`. If only a remote is
|
|
161
165
|
known, explain the clone destination and impact, obtain confirmation, then run
|
|
162
166
|
`project clone`; do not send the user mechanical clone steps.
|