@zq-silk/yui 0.6.13 → 0.6.14
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 +34 -5
- package/dist/cli/commandCatalog.js +173 -57
- package/dist/cli/helpRenderer.js +3 -1
- package/dist/cli.js +99 -11
- package/dist/commands/configCommands.js +521 -171
- package/dist/commands/deliveryGuardPreflight.js +2 -2
- package/dist/commands/executionAuditCommands.js +56 -3
- package/dist/commands/projectCommands.js +504 -2
- package/dist/commands/releaseCommands.js +0 -1
- package/dist/commands/taskActor.js +17 -0
- package/dist/commands/taskBaseCommands.js +29 -0
- package/dist/commands/taskCommands.js +577 -126
- package/dist/commands/taskContextCommand.js +36 -2
- package/dist/commands/taskNextActionCommand.js +48 -3
- package/dist/commands/taskPublicationCommands.js +319 -0
- package/dist/commands/taskRoleRuntimeStatus.js +83 -59
- package/dist/commands/telemetryCommands.js +14 -13
- package/dist/config/yuiConfig.js +161 -8
- package/dist/context/sessionContextBudget.js +68 -0
- package/dist/context/wakeNotification.js +65 -0
- package/dist/controller/clientRuntime.js +2 -1
- package/dist/controller/ephemeralResourceReaper.js +2 -1
- package/dist/controller/fileSchedulerStoreAdapter.js +183 -16
- package/dist/controller/jobSupervisor.js +5 -4
- package/dist/controller/resourceCleanupLinux.js +6 -6
- package/dist/controller/resourceInventoryLinux.js +3 -3
- package/dist/controller/runtime.js +88 -10
- package/dist/controller/updateReconciliation.js +4 -3
- package/dist/doctor/doctor.js +26 -7
- package/dist/executor/agentConfigurationCatalog.js +18 -0
- package/dist/executor/fileRoleLaunchPlanner.js +3 -3
- package/dist/lifecycle/contextBudgetRollover.js +81 -0
- package/dist/lifecycle/exactRunTerminalization.js +11 -1
- package/dist/lifecycle/providerErrorClass.js +33 -12
- package/dist/observability/executionAudit.js +214 -6
- package/dist/output/table.js +18 -0
- package/dist/repository/gitWorkspace.js +92 -0
- package/dist/repository/project.js +218 -4
- package/dist/repository/taskBaseFreshness.js +318 -0
- package/dist/repository/taskWorkspacePreparer.js +16 -2
- package/dist/review/deltaRecheck.js +232 -0
- package/dist/review/reviewConfig.js +31 -0
- package/dist/review/reviewFindingLedger.js +5 -1
- package/dist/review/reviewRound.js +156 -1
- package/dist/run/providerRetry.js +21 -3
- package/dist/run/providerRetryConfig.js +13 -60
- package/dist/run/recoveryProjection.js +199 -0
- package/dist/runtime/builtinAgentDrivers.js +3 -0
- package/dist/runtime/builtinTranscriptUsage.js +76 -32
- package/dist/runtime/continuationManager.js +17 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/launchDiagnostics.js +154 -0
- package/dist/runtime/lifecycleReservation.js +13 -0
- package/dist/runtime/providerContinuation.js +38 -0
- package/dist/runtime/providerContinuationReconciliationService.js +1 -0
- package/dist/runtime/providerErrorCodes.js +278 -0
- package/dist/runtime/runtimeHealthPolicy.js +20 -0
- package/dist/runtime/runtimeObservation.js +1 -0
- package/dist/runtime/runtimeProjection.js +115 -23
- package/dist/runtime/tmuxAdapters.js +242 -48
- package/dist/scheduler/activeRoleRunDelivery.js +139 -3
- package/dist/scheduler/activeTaskProgress.js +4 -3
- package/dist/scheduler/leaderWakeupProcessor.js +105 -15
- package/dist/scheduler/roleRunLiveness.js +2 -1
- package/dist/scheduler/roleRunStall.js +3 -2
- package/dist/scheduler/taskWake.js +72 -0
- package/dist/scheduler/wakeReason.js +64 -0
- package/dist/scheduler/wakeupQueue.js +2 -1
- package/dist/setup/setupCommand.js +1 -1
- package/dist/storage/migration/productionRegistry.js +325 -1
- package/dist/storage/sqliteSchema.js +61 -2
- package/dist/storage/sqliteStore.js +129 -2
- package/dist/storage/storeRpc.js +1 -0
- package/dist/storage/taskStore.js +262 -5
- package/dist/storage/upgrade/recordVersions.js +6 -1
- package/dist/storage/upgrade/sqliteStateMigration.js +22 -2
- package/dist/task/completionReadiness.js +282 -0
- package/dist/task/publicationReference.js +123 -0
- package/dist/task/taskRecordReference.js +3 -1
- package/dist/telemetry/telemetryConfig.js +23 -18
- package/dist/telemetry/telemetryWiring.js +8 -8
- package/dist/tmux/tmuxManager.js +50 -9
- package/dist/web/assets/client/i18n.js +4 -0
- package/dist/web/assets/client/view.js +18 -0
- package/dist/web/webSnapshot.js +100 -10
- package/i18n/README.zh-CN.md +5 -5
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +49 -10
- package/skills/yui-operator/SKILL.md +13 -3
- package/skills/yui-worker/SKILL.md +8 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveLeaderNextActionMode } from "../config/yuiConfig.js";
|
|
2
2
|
import { usageError } from "../errors/cliError.js";
|
|
3
3
|
import { detectDeliveryDuplicates, evaluateDeliveryGuard, evaluateSemanticBudget, formatDeliveryDuplicate } from "../task/deliveryGuard.js";
|
|
4
4
|
export function runDeliveryGuardPreflight(store, taskId, intent, options = {}) {
|
|
5
|
-
const mode =
|
|
5
|
+
const mode = resolveLeaderNextActionMode(store.getConfig().leaderNextActionMode);
|
|
6
6
|
if (mode === "display")
|
|
7
7
|
return { warnings: [] };
|
|
8
8
|
const facts = store.readNextActionFacts(taskId);
|
|
@@ -104,6 +104,15 @@ export function renderExecutionAudit(report, width = defaultTableWidth()) {
|
|
|
104
104
|
{ header: "Count", minWidth: 5, maxWidth: 8 }
|
|
105
105
|
], faultRows.map(([name, count]) => [name, String(count)]), width));
|
|
106
106
|
}
|
|
107
|
+
if (runs.launchFailures.total > 0) {
|
|
108
|
+
const phaseRows = Object.entries(runs.launchFailures.byPhase)
|
|
109
|
+
.filter(([, count]) => count > 0)
|
|
110
|
+
.sort((left, right) => right[1] - left[1]);
|
|
111
|
+
const kindRows = Object.entries(runs.launchFailures.byKind)
|
|
112
|
+
.filter(([, count]) => count > 0)
|
|
113
|
+
.sort((left, right) => right[1] - left[1]);
|
|
114
|
+
lines.push("", `Launch failures: ${runs.launchFailures.total}`, `By phase: ${phaseRows.map(([name, count]) => `${name}=${count}`).join(" · ")}`, `By kind: ${kindRows.map(([name, count]) => `${name}=${count}`).join(" · ")}`);
|
|
115
|
+
}
|
|
107
116
|
}
|
|
108
117
|
else {
|
|
109
118
|
lines.push("", ...sectionError("runs", report));
|
|
@@ -111,7 +120,10 @@ export function renderExecutionAudit(report, width = defaultTableWidth()) {
|
|
|
111
120
|
if (report.wakes.status === "ok" && report.wakes.data !== undefined) {
|
|
112
121
|
const wakes = report.wakes.data;
|
|
113
122
|
lines.push("", `Leader wakes: ${wakes.leaderRuns} runs · ${wakes.withWakeReasons} with reasons · ${wakes.orphanWakes} orphan wakes (${wakes.orphanYieldOnly} yield-only)`);
|
|
114
|
-
if (wakes.suppressedWakes.status === "
|
|
123
|
+
if (wakes.suppressedWakes.status === "ok") {
|
|
124
|
+
lines.push(`Suppressed wakes: ${wakes.suppressedWakes.data ?? 0} (scheduler single-flight, not failed Runs)`);
|
|
125
|
+
}
|
|
126
|
+
else if (wakes.suppressedWakes.status === "unsupported") {
|
|
115
127
|
lines.push("Suppressed wakes: unsupported (no quiescence producer in this build)");
|
|
116
128
|
}
|
|
117
129
|
const reasonRows = Object.entries(wakes.byReason).slice(0, 8);
|
|
@@ -127,14 +139,24 @@ export function renderExecutionAudit(report, width = defaultTableWidth()) {
|
|
|
127
139
|
}
|
|
128
140
|
if (report.sessions.status === "ok" && report.sessions.data !== undefined) {
|
|
129
141
|
const sessions = report.sessions.data;
|
|
130
|
-
lines.push("", `Sessions: ${sessions.generations} generations · ${sessions.broken} broken · ${sessions.stopped} stopped · ${sessions.other} other`, `Resets: ${sessions.resets} · lifecycle events ${sessions.lifecycleEvents} · stop failures ${sessions.stopFailures}`
|
|
142
|
+
lines.push("", `Sessions: ${sessions.generations} generations · ${sessions.broken} broken · ${sessions.stopped} stopped · ${sessions.other} other`, `Resets: ${sessions.resets} · lifecycle events ${sessions.lifecycleEvents} · stop failures ${sessions.stopFailures}`, `Terminal by Run relation: ${sessions.terminalByRunRelation.postRunYielded} post-run-yielded`
|
|
143
|
+
+ ` · ${sessions.terminalByRunRelation.runFailed} run-failed`
|
|
144
|
+
+ ` · ${sessions.terminalByRunRelation.activeRun} active-run`
|
|
145
|
+
+ ` · ${sessions.terminalByRunRelation.noRun} no-run`);
|
|
131
146
|
}
|
|
132
147
|
else {
|
|
133
148
|
lines.push("", ...sectionError("sessions", report));
|
|
134
149
|
}
|
|
135
150
|
if (report.reviews.status === "ok" && report.reviews.data !== undefined) {
|
|
136
151
|
const reviews = report.reviews.data;
|
|
137
|
-
lines.push("", `Review rounds: ${reviews.total} total · ${reviews.completed} completed · ${reviews.failed} failed`, `Execution failures: ${reviews.infraFailed} · semantic negatives: ${reviews.semanticNegative}
|
|
152
|
+
lines.push("", `Review rounds: ${reviews.total} total · ${reviews.completed} completed · ${reviews.failed} failed`, `Execution failures: ${reviews.infraFailed} · semantic negatives: ${reviews.semanticNegative}`, ...(reviews.deltaRechecks.total === 0
|
|
153
|
+
? []
|
|
154
|
+
: [
|
|
155
|
+
`Delta-rechecks: ${reviews.deltaRechecks.total} total · `
|
|
156
|
+
+ `${reviews.deltaRechecks.equivalentAndAccepted} accepted · `
|
|
157
|
+
+ `${reviews.deltaRechecks.finding} finding · `
|
|
158
|
+
+ `${reviews.deltaRechecks.requiresFullReview} escalated to full Review`
|
|
159
|
+
]));
|
|
138
160
|
}
|
|
139
161
|
else {
|
|
140
162
|
lines.push("", ...sectionError("reviews", report));
|
|
@@ -146,6 +168,13 @@ export function renderExecutionAudit(report, width = defaultTableWidth()) {
|
|
|
146
168
|
else {
|
|
147
169
|
lines.push("", ...sectionError("integrations", report));
|
|
148
170
|
}
|
|
171
|
+
if (report.publications.status === "ok" && report.publications.data !== undefined) {
|
|
172
|
+
const publications = report.publications.data;
|
|
173
|
+
lines.push("", `Publication references: ${publications.total} total · ${publications.merged} merged · ${publications.verified} verified · ${publications.open} open · ${publications.closed} closed · ${publications.superseded} superseded`);
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
lines.push("", ...sectionError("publications", report));
|
|
177
|
+
}
|
|
149
178
|
if (report.events.status === "ok" && report.events.data !== undefined) {
|
|
150
179
|
const events = report.events.data;
|
|
151
180
|
lines.push("", `Events: ${events.total} total · ${events.progressEvents} provider-progress (${(events.progressShare * 100).toFixed(1)}%) · ${events.semanticEvents} semantic · ${events.obsoleteEvents} obsolete`, `Messages: ${events.messages}`);
|
|
@@ -153,6 +182,30 @@ export function renderExecutionAudit(report, width = defaultTableWidth()) {
|
|
|
153
182
|
else {
|
|
154
183
|
lines.push("", ...sectionError("events", report));
|
|
155
184
|
}
|
|
185
|
+
if (report.providerRetries.status === "ok" && report.providerRetries.data !== undefined) {
|
|
186
|
+
const retries = report.providerRetries.data;
|
|
187
|
+
if (retries.total > 0) {
|
|
188
|
+
lines.push("", `Provider retries: ${retries.total} run(s) retried in place · ${retries.terminal} terminal`);
|
|
189
|
+
lines.push(renderTable("Provider retry lineages", [
|
|
190
|
+
{ header: "Task", minWidth: 8, maxWidth: 14 },
|
|
191
|
+
{ header: "Run", minWidth: 14, maxWidth: 24 },
|
|
192
|
+
{ header: "Role", minWidth: 8, maxWidth: 12 },
|
|
193
|
+
{ header: "Attempts", minWidth: 8, maxWidth: 10 },
|
|
194
|
+
{ header: "Error class", minWidth: 14, maxWidth: 24 },
|
|
195
|
+
{ header: "Decision", minWidth: 14, maxWidth: 26 }
|
|
196
|
+
], retries.entries.map((entry) => [
|
|
197
|
+
entry.taskId,
|
|
198
|
+
entry.runId,
|
|
199
|
+
entry.roleName,
|
|
200
|
+
String(entry.attempts),
|
|
201
|
+
entry.errorClass,
|
|
202
|
+
entry.decision
|
|
203
|
+
]), width));
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
lines.push("", ...sectionError("providerRetries", report));
|
|
208
|
+
}
|
|
156
209
|
if (report.workItems.status === "ok" && report.workItems.data !== undefined) {
|
|
157
210
|
const items = report.workItems.data;
|
|
158
211
|
lines.push("", `Work items: ${items.total} total · ${items.completed} completed · ${items.retired} retired`);
|
|
@@ -5,7 +5,8 @@ import { usageError } from "../errors/cliError.js";
|
|
|
5
5
|
import { defaultTableWidth, renderTable } from "../output/table.js";
|
|
6
6
|
import { NodeGitWorkspace } from "../repository/gitWorkspace.js";
|
|
7
7
|
import { acquireProjectMaintenanceLock } from "../repository/projectMaintenanceLock.js";
|
|
8
|
-
import { addProjectKnowledge, createProject, managedProjectPath, retireProjectKnowledge, resolveProject, updateProjectKnowledge, updateProjectMetadata, validateProject, validateProjectName } from "../repository/project.js";
|
|
8
|
+
import { addProjectKnowledge, addKnowledgeProposal, decideKnowledgeProposal, createProject, findKnowledgeProposal, findKnowledgeProposalByFingerprint, knowledgeEvidenceDigest, knowledgeProposalFingerprint, managedProjectPath, planKnowledgeAcceptance, retireProjectKnowledge, resolveProject, updateProjectKnowledge, updateProjectMetadata, validateProject, validateProjectName } from "../repository/project.js";
|
|
9
|
+
import { projectActor } from "./taskActor.js";
|
|
9
10
|
export async function runProjectCommand(args, store, options = {}) {
|
|
10
11
|
const [command, ...rest] = args;
|
|
11
12
|
if (command === "add" || command === "clone") {
|
|
@@ -521,7 +522,8 @@ function showProject(args, store) {
|
|
|
521
522
|
...(project.remoteUrl === undefined ? [] : [`Remote: ${project.remoteUrl}`]),
|
|
522
523
|
`Stable: ${project.stableBranch}`,
|
|
523
524
|
`Development: ${project.developmentBranch}`,
|
|
524
|
-
`Knowledge: ${project.knowledge.filter(({ status }) => status === "active").length} active
|
|
525
|
+
`Knowledge: ${project.knowledge.filter(({ status }) => status === "active").length} active`,
|
|
526
|
+
`Knowledge proposals: ${project.knowledgeProposals.filter(({ status }) => status === "pending").length} pending`
|
|
525
527
|
].join("\n").concat("\n");
|
|
526
528
|
}
|
|
527
529
|
function projectKnowledge(args, store, options) {
|
|
@@ -531,6 +533,7 @@ function projectKnowledge(args, store, options) {
|
|
|
531
533
|
const parsed = parseSingleOption(rest, "--body", usage);
|
|
532
534
|
if (parsed.positionals.length !== 2)
|
|
533
535
|
throw usageError(usage);
|
|
536
|
+
assertKnowledgeOperator(options, "add");
|
|
534
537
|
const added = store.transaction((tx) => {
|
|
535
538
|
const project = requireProject(tx, parsed.positionals[0]);
|
|
536
539
|
const id = nextKnowledgeId(project);
|
|
@@ -584,6 +587,21 @@ function projectKnowledge(args, store, options) {
|
|
|
584
587
|
`Project: ${project.id}`,
|
|
585
588
|
`Title: ${entry.title}`,
|
|
586
589
|
`Status: ${entry.status}`,
|
|
590
|
+
...(entry.provenance === undefined ? [] : [
|
|
591
|
+
`Source: ${entry.provenance.taskId}`
|
|
592
|
+
+ `${entry.provenance.decisionId === undefined ? "" : `/${entry.provenance.decisionId}`}`
|
|
593
|
+
+ `${entry.provenance.milestoneId === undefined ? "" : `/${entry.provenance.milestoneId}`}`
|
|
594
|
+
+ `${entry.provenance.commitSha === undefined ? "" : `@${entry.provenance.commitSha}`}`,
|
|
595
|
+
...(entry.provenance.evidenceDigest === undefined
|
|
596
|
+
? []
|
|
597
|
+
: [`Evidence digest: ${entry.provenance.evidenceDigest}`]),
|
|
598
|
+
...(entry.provenance.proposalId === undefined
|
|
599
|
+
? []
|
|
600
|
+
: [`Proposal: ${entry.provenance.proposalId}`]),
|
|
601
|
+
...(entry.provenance.promotedBy === undefined
|
|
602
|
+
? []
|
|
603
|
+
: [`Promoted by: ${entry.provenance.promotedBy} at ${entry.provenance.promotedAt ?? "?"}`])
|
|
604
|
+
]),
|
|
587
605
|
"",
|
|
588
606
|
entry.body
|
|
589
607
|
].join("\n").concat("\n"),
|
|
@@ -594,6 +612,7 @@ function projectKnowledge(args, store, options) {
|
|
|
594
612
|
if (command === "update") {
|
|
595
613
|
const usage = "Project knowledge update usage: yui project knowledge update <project> <knowledge> [--title <text>] [--body <text>].";
|
|
596
614
|
const parsed = parseKnowledgeUpdateArguments(rest, usage);
|
|
615
|
+
assertKnowledgeOperator(options, "update");
|
|
597
616
|
const updated = store.transaction((tx) => {
|
|
598
617
|
const project = requireProject(tx, parsed.project);
|
|
599
618
|
const next = updateProjectKnowledge(project, parsed.id, {
|
|
@@ -613,6 +632,7 @@ function projectKnowledge(args, store, options) {
|
|
|
613
632
|
if (rest.length !== 2) {
|
|
614
633
|
throw usageError("Project knowledge retire usage: yui project knowledge retire <project> <knowledge>.");
|
|
615
634
|
}
|
|
635
|
+
assertKnowledgeOperator(options, "retire");
|
|
616
636
|
const updated = store.transaction((tx) => {
|
|
617
637
|
const project = requireProject(tx, rest[0]);
|
|
618
638
|
const next = retireProjectKnowledge(project, rest[1], (options.now ?? (() => new Date()))());
|
|
@@ -625,6 +645,24 @@ function projectKnowledge(args, store, options) {
|
|
|
625
645
|
knowledgeId: rest[1]
|
|
626
646
|
};
|
|
627
647
|
}
|
|
648
|
+
if (command === "propose") {
|
|
649
|
+
return proposeKnowledge(rest, store, options);
|
|
650
|
+
}
|
|
651
|
+
if (command === "proposals") {
|
|
652
|
+
const [sub, ...subRest] = rest;
|
|
653
|
+
if (sub === "list")
|
|
654
|
+
return listKnowledgeProposals(subRest, store);
|
|
655
|
+
if (sub === "show")
|
|
656
|
+
return showKnowledgeProposal(subRest, store);
|
|
657
|
+
throw usageError("Project knowledge proposals usage: yui project knowledge proposals list <project>"
|
|
658
|
+
+ " [--status pending|accepted|rejected] [--all] | yui project knowledge proposals show <project> <proposal>.");
|
|
659
|
+
}
|
|
660
|
+
if (command === "accept") {
|
|
661
|
+
return acceptKnowledgeProposal(rest, store, options);
|
|
662
|
+
}
|
|
663
|
+
if (command === "reject") {
|
|
664
|
+
return rejectKnowledgeProposal(rest, store, options);
|
|
665
|
+
}
|
|
628
666
|
throw usageError(command === undefined
|
|
629
667
|
? "Project knowledge command is required."
|
|
630
668
|
: `Unknown command: project knowledge ${command}`);
|
|
@@ -643,6 +681,470 @@ function nextKnowledgeId(project) {
|
|
|
643
681
|
return id;
|
|
644
682
|
}
|
|
645
683
|
}
|
|
684
|
+
function nextProposalId(project) {
|
|
685
|
+
const used = new Set(project.knowledgeProposals.map(({ id }) => id));
|
|
686
|
+
for (let index = 1;; index += 1) {
|
|
687
|
+
const id = `proposal-${index}`;
|
|
688
|
+
if (!used.has(id))
|
|
689
|
+
return id;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Project Knowledge is an Operator authority. A managed Task Session (Leader,
|
|
694
|
+
* Reviewer, Worker) must use `propose` and let an Operator accept; it can
|
|
695
|
+
* never write the authoritative list directly.
|
|
696
|
+
*/
|
|
697
|
+
function assertKnowledgeOperator(options, action) {
|
|
698
|
+
const actor = projectActor(options.environment);
|
|
699
|
+
if (actor === "agent") {
|
|
700
|
+
throw usageError(`Project Knowledge is an Operator authority; a managed Task Session cannot ${action} it.`
|
|
701
|
+
+ " Propose a candidate with `yui project knowledge propose` instead.");
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
const PROPOSE_OPTIONS = [
|
|
705
|
+
"--title", "--body", "--task", "--decision", "--milestone",
|
|
706
|
+
"--commit", "--scope", "--expires-when", "--supersedes"
|
|
707
|
+
];
|
|
708
|
+
function parseKnowledgeProposalOptions(args, usage) {
|
|
709
|
+
const values = {};
|
|
710
|
+
const positionals = [];
|
|
711
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
712
|
+
const value = args[index];
|
|
713
|
+
if (PROPOSE_OPTIONS.includes(value)) {
|
|
714
|
+
if (values[value] !== undefined) {
|
|
715
|
+
throw usageError(`${value} may only be provided once. ${usage}`);
|
|
716
|
+
}
|
|
717
|
+
const next = args[index + 1];
|
|
718
|
+
if (next === undefined || next.startsWith("--")) {
|
|
719
|
+
throw usageError(`${value} is required. ${usage}`);
|
|
720
|
+
}
|
|
721
|
+
values[value] = requireText(next, value);
|
|
722
|
+
index += 1;
|
|
723
|
+
}
|
|
724
|
+
else if (value.startsWith("--")) {
|
|
725
|
+
throw usageError(`Unknown option: ${value}. ${usage}`);
|
|
726
|
+
}
|
|
727
|
+
else {
|
|
728
|
+
positionals.push(value);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
if (positionals.length !== 1)
|
|
732
|
+
throw usageError(usage);
|
|
733
|
+
if (values["--title"] === undefined || values["--body"] === undefined || values["--task"] === undefined) {
|
|
734
|
+
throw usageError(usage);
|
|
735
|
+
}
|
|
736
|
+
return {
|
|
737
|
+
project: positionals[0],
|
|
738
|
+
title: values["--title"],
|
|
739
|
+
body: values["--body"],
|
|
740
|
+
task: values["--task"],
|
|
741
|
+
...(values["--decision"] === undefined ? {} : { decision: values["--decision"] }),
|
|
742
|
+
...(values["--milestone"] === undefined ? {} : { milestone: values["--milestone"] }),
|
|
743
|
+
...(values["--commit"] === undefined ? {} : { commit: values["--commit"] }),
|
|
744
|
+
...(values["--scope"] === undefined ? {} : { scope: values["--scope"] }),
|
|
745
|
+
...(values["--expires-when"] === undefined ? {} : { expiresWhen: values["--expires-when"] }),
|
|
746
|
+
...(values["--supersedes"] === undefined ? {} : { supersedes: values["--supersedes"] })
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* Leader-proposed promotion candidate. The proposal is workflow state, not
|
|
751
|
+
* Knowledge: it never appears in `project knowledge list/show` until an
|
|
752
|
+
* Operator accepts it. A managed Task Session may propose only for its own
|
|
753
|
+
* Task. The same candidate (source identity + title + body) is deduplicated to
|
|
754
|
+
* the existing pending proposal instead of creating a second copy.
|
|
755
|
+
*/
|
|
756
|
+
function proposeKnowledge(args, store, options) {
|
|
757
|
+
const usage = "Project knowledge propose usage: yui project knowledge propose <project>"
|
|
758
|
+
+ " --title <text> --body <text> --task <task> [--decision <id>] [--milestone <id>]"
|
|
759
|
+
+ " [--commit <sha>] [--scope <text>] [--expires-when <text>] [--supersedes <knowledge-id>].";
|
|
760
|
+
const parsed = parseKnowledgeProposalOptions(args, usage);
|
|
761
|
+
const actor = projectActor(options.environment);
|
|
762
|
+
if (actor === "agent" && options.environment?.YUI_TASK_ID !== parsed.task) {
|
|
763
|
+
throw usageError("A managed Task Session may propose Knowledge only for its own Task.");
|
|
764
|
+
}
|
|
765
|
+
if (actor === "agent"
|
|
766
|
+
&& options.environment?.YUI_ROLE !== "leader"
|
|
767
|
+
&& options.environment?.YUI_ROLE !== "reviewer") {
|
|
768
|
+
throw usageError("Only a Leader or Reviewer Session may propose Knowledge promotion.");
|
|
769
|
+
}
|
|
770
|
+
const proposedBy = actor === "agent"
|
|
771
|
+
? (options.environment?.YUI_ROLE === "reviewer" ? "reviewer" : "leader")
|
|
772
|
+
: actor;
|
|
773
|
+
const now = (options.now ?? (() => new Date()))();
|
|
774
|
+
const result = store.transaction((tx) => {
|
|
775
|
+
const project = requireProject(tx, parsed.project);
|
|
776
|
+
const source = requireProposalEvidence(tx, parsed);
|
|
777
|
+
const fingerprint = knowledgeProposalFingerprint({
|
|
778
|
+
projectId: project.id,
|
|
779
|
+
source,
|
|
780
|
+
title: parsed.title,
|
|
781
|
+
body: parsed.body
|
|
782
|
+
});
|
|
783
|
+
const pending = findKnowledgeProposalByFingerprint(project, fingerprint, "pending");
|
|
784
|
+
if (pending !== null) {
|
|
785
|
+
return {
|
|
786
|
+
projectId: project.id,
|
|
787
|
+
proposalId: pending.id,
|
|
788
|
+
deduped: true,
|
|
789
|
+
alreadyAccepted: false
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
const accepted = findKnowledgeProposalByFingerprint(project, fingerprint, "accepted");
|
|
793
|
+
if (accepted !== null) {
|
|
794
|
+
return {
|
|
795
|
+
projectId: project.id,
|
|
796
|
+
proposalId: accepted.id,
|
|
797
|
+
knowledgeId: accepted.knowledgeId,
|
|
798
|
+
deduped: true,
|
|
799
|
+
alreadyAccepted: true
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
const proposal = {
|
|
803
|
+
schemaVersion: 1,
|
|
804
|
+
id: nextProposalId(project),
|
|
805
|
+
projectId: project.id,
|
|
806
|
+
title: parsed.title,
|
|
807
|
+
body: parsed.body,
|
|
808
|
+
status: "pending",
|
|
809
|
+
source,
|
|
810
|
+
...(parsed.scope === undefined ? {} : { scope: parsed.scope }),
|
|
811
|
+
...(parsed.expiresWhen === undefined ? {} : { expiresWhen: parsed.expiresWhen }),
|
|
812
|
+
...(parsed.supersedes === undefined ? {} : { supersedesKnowledgeId: parsed.supersedes }),
|
|
813
|
+
fingerprint,
|
|
814
|
+
...(source.evidenceDigest === undefined ? {} : { evidenceDigest: source.evidenceDigest }),
|
|
815
|
+
proposedBy,
|
|
816
|
+
proposedAt: now.toISOString(),
|
|
817
|
+
updatedAt: now.toISOString()
|
|
818
|
+
};
|
|
819
|
+
tx.saveProject(addKnowledgeProposal(project, proposal));
|
|
820
|
+
return {
|
|
821
|
+
projectId: project.id,
|
|
822
|
+
proposalId: proposal.id,
|
|
823
|
+
deduped: false,
|
|
824
|
+
alreadyAccepted: false
|
|
825
|
+
};
|
|
826
|
+
});
|
|
827
|
+
const lines = [
|
|
828
|
+
result.deduped
|
|
829
|
+
? `Knowledge proposal ${result.proposalId} already exists for ${result.projectId}`
|
|
830
|
+
: `Proposed knowledge ${result.proposalId} for ${result.projectId}`,
|
|
831
|
+
result.alreadyAccepted
|
|
832
|
+
? `Already accepted as knowledge ${result.knowledgeId ?? "?"}`
|
|
833
|
+
: "Awaiting Operator decision: yui project knowledge proposals list "
|
|
834
|
+
+ `${result.projectId} / yui project knowledge accept ${result.projectId} ${result.proposalId}`
|
|
835
|
+
];
|
|
836
|
+
return {
|
|
837
|
+
output: `${lines.join("\n")}\n`,
|
|
838
|
+
projectId: result.projectId,
|
|
839
|
+
proposalId: result.proposalId,
|
|
840
|
+
...(result.knowledgeId === undefined ? {} : { knowledgeId: result.knowledgeId })
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Verify the cited Task/Decision/Milestone exist and capture the evidence
|
|
845
|
+
* digest at proposal time, so a later Agent can verify the source record.
|
|
846
|
+
*/
|
|
847
|
+
function requireProposalEvidence(store, parsed) {
|
|
848
|
+
if (store.getTask === undefined) {
|
|
849
|
+
throw usageError("Knowledge promotion requires Task evidence, which this store does not provide.");
|
|
850
|
+
}
|
|
851
|
+
const task = store.getTask(parsed.task);
|
|
852
|
+
if (task === null)
|
|
853
|
+
throw usageError(`Task not found: ${parsed.task}.`);
|
|
854
|
+
const source = {
|
|
855
|
+
taskId: task.id,
|
|
856
|
+
...(parsed.decision === undefined ? {} : { decisionId: parsed.decision }),
|
|
857
|
+
...(parsed.milestone === undefined ? {} : { milestoneId: parsed.milestone }),
|
|
858
|
+
...(parsed.commit === undefined ? {} : { commitSha: parsed.commit })
|
|
859
|
+
};
|
|
860
|
+
let evidence;
|
|
861
|
+
if (parsed.decision !== undefined) {
|
|
862
|
+
if (store.getDecision === undefined) {
|
|
863
|
+
throw usageError("Knowledge promotion requires Decision evidence, which this store does not provide.");
|
|
864
|
+
}
|
|
865
|
+
const decision = store.getDecision(task.id, parsed.decision);
|
|
866
|
+
if (decision === null) {
|
|
867
|
+
throw usageError(`Decision not found: ${parsed.decision} in Task ${task.id}.`);
|
|
868
|
+
}
|
|
869
|
+
evidence = `${decision.title}\n${decision.rationale}`;
|
|
870
|
+
}
|
|
871
|
+
else if (parsed.milestone !== undefined) {
|
|
872
|
+
if (store.getMilestone === undefined) {
|
|
873
|
+
throw usageError("Knowledge promotion requires Milestone evidence, which this store does not provide.");
|
|
874
|
+
}
|
|
875
|
+
const milestone = store.getMilestone(task.id, parsed.milestone);
|
|
876
|
+
if (milestone === null) {
|
|
877
|
+
throw usageError(`Milestone not found: ${parsed.milestone} in Task ${task.id}.`);
|
|
878
|
+
}
|
|
879
|
+
evidence = `${milestone.title}\n${milestone.summary}`;
|
|
880
|
+
}
|
|
881
|
+
else {
|
|
882
|
+
evidence = task.completionSummary ?? task.title;
|
|
883
|
+
}
|
|
884
|
+
return { ...source, evidenceDigest: knowledgeEvidenceDigest(evidence) };
|
|
885
|
+
}
|
|
886
|
+
function listKnowledgeProposals(args, store) {
|
|
887
|
+
const usage = "Project knowledge proposals list usage: yui project knowledge proposals list <project>"
|
|
888
|
+
+ " [--status pending|accepted|rejected] [--all].";
|
|
889
|
+
const positionals = [];
|
|
890
|
+
let status = "pending";
|
|
891
|
+
let sawAll = false;
|
|
892
|
+
let sawStatus = false;
|
|
893
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
894
|
+
const value = args[index];
|
|
895
|
+
if (value === "--all") {
|
|
896
|
+
if (sawStatus)
|
|
897
|
+
throw usageError(`--all and --status are mutually exclusive. ${usage}`);
|
|
898
|
+
sawAll = true;
|
|
899
|
+
status = undefined;
|
|
900
|
+
}
|
|
901
|
+
else if (value === "--status") {
|
|
902
|
+
if (sawAll)
|
|
903
|
+
throw usageError(`--all and --status are mutually exclusive. ${usage}`);
|
|
904
|
+
sawStatus = true;
|
|
905
|
+
const next = args[index + 1];
|
|
906
|
+
if (next === undefined || next.startsWith("--"))
|
|
907
|
+
throw usageError(usage);
|
|
908
|
+
if (next !== "pending" && next !== "accepted" && next !== "rejected")
|
|
909
|
+
throw usageError(usage);
|
|
910
|
+
status = next;
|
|
911
|
+
index += 1;
|
|
912
|
+
}
|
|
913
|
+
else if (value.startsWith("--")) {
|
|
914
|
+
throw usageError(`Unknown option: ${value}. ${usage}`);
|
|
915
|
+
}
|
|
916
|
+
else {
|
|
917
|
+
positionals.push(value);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
if (positionals.length !== 1)
|
|
921
|
+
throw usageError(usage);
|
|
922
|
+
const project = requireProject(store, positionals[0]);
|
|
923
|
+
const entries = project.knowledgeProposals
|
|
924
|
+
.filter((entry) => status === undefined || entry.status === status)
|
|
925
|
+
.slice()
|
|
926
|
+
.sort((a, b) => a.id.localeCompare(b.id));
|
|
927
|
+
if (entries.length === 0) {
|
|
928
|
+
return {
|
|
929
|
+
output: "No knowledge proposals found.\n",
|
|
930
|
+
projectId: project.id,
|
|
931
|
+
proposals: []
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
return {
|
|
935
|
+
output: `${renderTable(`Knowledge proposals: ${project.name}`, [
|
|
936
|
+
{ header: "Proposal", minWidth: 9, maxWidth: 24 },
|
|
937
|
+
{ header: "Title", minWidth: 8, maxWidth: 48 },
|
|
938
|
+
{ header: "Status", minWidth: 7, maxWidth: 10 },
|
|
939
|
+
{ header: "Source", minWidth: 8, maxWidth: 32 },
|
|
940
|
+
{ header: "Knowledge", minWidth: 9, maxWidth: 24 }
|
|
941
|
+
], entries.map((entry) => [
|
|
942
|
+
entry.id,
|
|
943
|
+
entry.title,
|
|
944
|
+
entry.status,
|
|
945
|
+
`${entry.source.taskId}`
|
|
946
|
+
+ `${entry.source.decisionId === undefined ? "" : `/${entry.source.decisionId}`}`
|
|
947
|
+
+ `${entry.source.milestoneId === undefined ? "" : `/${entry.source.milestoneId}`}`,
|
|
948
|
+
entry.knowledgeId ?? "-"
|
|
949
|
+
]), defaultTableWidth())}\n`,
|
|
950
|
+
projectId: project.id,
|
|
951
|
+
proposals: entries
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
function showKnowledgeProposal(args, store) {
|
|
955
|
+
const usage = "Project knowledge proposals show usage: yui project knowledge proposals show <project> <proposal>.";
|
|
956
|
+
if (args.length !== 2)
|
|
957
|
+
throw usageError(usage);
|
|
958
|
+
const project = requireProject(store, args[0]);
|
|
959
|
+
const proposal = findKnowledgeProposal(project, args[1]);
|
|
960
|
+
if (proposal === null) {
|
|
961
|
+
throw usageError(`Knowledge proposal not found: ${args[1]}.`);
|
|
962
|
+
}
|
|
963
|
+
const lines = [
|
|
964
|
+
`Proposal: ${proposal.id}`,
|
|
965
|
+
`Project: ${project.id}`,
|
|
966
|
+
`Title: ${proposal.title}`,
|
|
967
|
+
`Status: ${proposal.status}`,
|
|
968
|
+
`Source: ${proposal.source.taskId}`
|
|
969
|
+
+ `${proposal.source.decisionId === undefined ? "" : `/${proposal.source.decisionId}`}`
|
|
970
|
+
+ `${proposal.source.milestoneId === undefined ? "" : `/${proposal.source.milestoneId}`}`
|
|
971
|
+
+ `${proposal.source.commitSha === undefined ? "" : `@${proposal.source.commitSha}`}`,
|
|
972
|
+
`Proposed by: ${proposal.proposedBy} at ${proposal.proposedAt}`,
|
|
973
|
+
...(proposal.scope === undefined ? [] : [`Scope: ${proposal.scope}`]),
|
|
974
|
+
...(proposal.expiresWhen === undefined ? [] : [`Expires when: ${proposal.expiresWhen}`]),
|
|
975
|
+
...(proposal.supersedesKnowledgeId === undefined
|
|
976
|
+
? []
|
|
977
|
+
: [`Supersedes: ${proposal.supersedesKnowledgeId}`]),
|
|
978
|
+
...(proposal.evidenceDigest === undefined ? [] : [`Evidence digest: ${proposal.evidenceDigest}`]),
|
|
979
|
+
...(proposal.decidedBy === undefined ? [] : [`Decided by: ${proposal.decidedBy} at ${proposal.decidedAt ?? "?"}`]),
|
|
980
|
+
...(proposal.decisionReason === undefined ? [] : [`Decision reason: ${proposal.decisionReason}`]),
|
|
981
|
+
...(proposal.knowledgeId === undefined ? [] : [`Knowledge: ${proposal.knowledgeId}`]),
|
|
982
|
+
"",
|
|
983
|
+
proposal.body
|
|
984
|
+
];
|
|
985
|
+
return {
|
|
986
|
+
output: `${lines.join("\n")}\n`,
|
|
987
|
+
projectId: project.id,
|
|
988
|
+
proposalId: proposal.id,
|
|
989
|
+
proposals: [proposal]
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* Operator acceptance. The pure {@link planKnowledgeAcceptance} policy decides
|
|
994
|
+
* create/update/duplicate/conflict; a conflict fails closed so a candidate can
|
|
995
|
+
* never silently overwrite an existing conclusion. The accepted Knowledge
|
|
996
|
+
* entry carries the source provenance and fingerprint, and the proposal record
|
|
997
|
+
* retains the decision for history.
|
|
998
|
+
*/
|
|
999
|
+
function acceptKnowledgeProposal(args, store, options) {
|
|
1000
|
+
const usage = "Project knowledge accept usage: yui project knowledge accept <project> <proposal>"
|
|
1001
|
+
+ " [--update <knowledge-id>].";
|
|
1002
|
+
const parsed = parseAcceptArguments(args, usage);
|
|
1003
|
+
assertKnowledgeOperator(options, "accept");
|
|
1004
|
+
const actor = projectActor(options.environment);
|
|
1005
|
+
const decidedBy = actor === "operator" ? "operator" : "user";
|
|
1006
|
+
const now = (options.now ?? (() => new Date()))();
|
|
1007
|
+
const accepted = store.transaction((tx) => {
|
|
1008
|
+
const project = requireProject(tx, parsed.project);
|
|
1009
|
+
const proposal = findKnowledgeProposal(project, parsed.proposal);
|
|
1010
|
+
if (proposal === null) {
|
|
1011
|
+
throw usageError(`Knowledge proposal not found: ${parsed.proposal}.`);
|
|
1012
|
+
}
|
|
1013
|
+
if (proposal.status !== "pending") {
|
|
1014
|
+
throw usageError(`Knowledge proposal is already ${proposal.status}: ${proposal.id}.`);
|
|
1015
|
+
}
|
|
1016
|
+
let next = project;
|
|
1017
|
+
let knowledgeId;
|
|
1018
|
+
let duplicate = false;
|
|
1019
|
+
if (parsed.update !== undefined) {
|
|
1020
|
+
const target = next.knowledge.find((entry) => entry.id === parsed.update);
|
|
1021
|
+
if (target === undefined) {
|
|
1022
|
+
throw usageError(`Knowledge not found: ${parsed.update}.`);
|
|
1023
|
+
}
|
|
1024
|
+
if (target.status !== "active") {
|
|
1025
|
+
throw usageError(`Knowledge is not active: ${target.id}.`);
|
|
1026
|
+
}
|
|
1027
|
+
knowledgeId = target.id;
|
|
1028
|
+
next = updateProjectKnowledge(next, knowledgeId, {
|
|
1029
|
+
title: proposal.title,
|
|
1030
|
+
body: proposal.body
|
|
1031
|
+
}, now);
|
|
1032
|
+
next = withKnowledgeProvenance(next, knowledgeId, {
|
|
1033
|
+
...provenanceFromProposal(proposal),
|
|
1034
|
+
proposalId: proposal.id,
|
|
1035
|
+
promotedBy: decidedBy,
|
|
1036
|
+
promotedAt: now.toISOString()
|
|
1037
|
+
}, now);
|
|
1038
|
+
}
|
|
1039
|
+
else {
|
|
1040
|
+
const plan = planKnowledgeAcceptance(next, proposal);
|
|
1041
|
+
if (plan.kind === "duplicate") {
|
|
1042
|
+
knowledgeId = plan.knowledgeId;
|
|
1043
|
+
duplicate = true;
|
|
1044
|
+
}
|
|
1045
|
+
else {
|
|
1046
|
+
if (plan.kind === "create" && plan.supersedesKnowledgeId !== undefined) {
|
|
1047
|
+
next = retireProjectKnowledge(next, plan.supersedesKnowledgeId, now);
|
|
1048
|
+
}
|
|
1049
|
+
knowledgeId = nextKnowledgeId(next);
|
|
1050
|
+
next = addProjectKnowledge(next, knowledgeId, proposal.title, proposal.body, now, {
|
|
1051
|
+
...provenanceFromProposal(proposal),
|
|
1052
|
+
proposalId: proposal.id,
|
|
1053
|
+
promotedBy: decidedBy,
|
|
1054
|
+
promotedAt: now.toISOString()
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
next = decideKnowledgeProposal(next, proposal.id, {
|
|
1059
|
+
by: decidedBy,
|
|
1060
|
+
knowledgeId
|
|
1061
|
+
}, now);
|
|
1062
|
+
tx.saveProject(next);
|
|
1063
|
+
return { projectId: next.id, proposalId: proposal.id, knowledgeId, duplicate };
|
|
1064
|
+
});
|
|
1065
|
+
return {
|
|
1066
|
+
output: `${accepted.duplicate
|
|
1067
|
+
? `Knowledge proposal ${accepted.proposalId} was already promoted as ${accepted.knowledgeId}`
|
|
1068
|
+
: `Accepted knowledge proposal ${accepted.proposalId} as ${accepted.knowledgeId}`} in ${accepted.projectId}\n`,
|
|
1069
|
+
projectId: accepted.projectId,
|
|
1070
|
+
proposalId: accepted.proposalId,
|
|
1071
|
+
knowledgeId: accepted.knowledgeId
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
function rejectKnowledgeProposal(args, store, options) {
|
|
1075
|
+
const usage = "Project knowledge reject usage: yui project knowledge reject <project> <proposal>"
|
|
1076
|
+
+ " --reason <text>.";
|
|
1077
|
+
const parsed = parseSingleOption(args, "--reason", usage);
|
|
1078
|
+
if (parsed.positionals.length !== 2)
|
|
1079
|
+
throw usageError(usage);
|
|
1080
|
+
assertKnowledgeOperator(options, "reject");
|
|
1081
|
+
const actor = projectActor(options.environment);
|
|
1082
|
+
const decidedBy = actor === "operator" ? "operator" : "user";
|
|
1083
|
+
const rejected = store.transaction((tx) => {
|
|
1084
|
+
const project = requireProject(tx, parsed.positionals[0]);
|
|
1085
|
+
const proposal = findKnowledgeProposal(project, parsed.positionals[1]);
|
|
1086
|
+
if (proposal === null) {
|
|
1087
|
+
throw usageError(`Knowledge proposal not found: ${parsed.positionals[1]}.`);
|
|
1088
|
+
}
|
|
1089
|
+
if (proposal.status !== "pending") {
|
|
1090
|
+
throw usageError(`Knowledge proposal is already ${proposal.status}: ${proposal.id}.`);
|
|
1091
|
+
}
|
|
1092
|
+
const next = decideKnowledgeProposal(project, proposal.id, {
|
|
1093
|
+
by: decidedBy,
|
|
1094
|
+
reason: parsed.value
|
|
1095
|
+
}, (options.now ?? (() => new Date()))());
|
|
1096
|
+
tx.saveProject(next);
|
|
1097
|
+
return { projectId: next.id, proposalId: proposal.id };
|
|
1098
|
+
});
|
|
1099
|
+
return {
|
|
1100
|
+
output: `Rejected knowledge proposal ${rejected.proposalId} in ${rejected.projectId}\n`,
|
|
1101
|
+
projectId: rejected.projectId,
|
|
1102
|
+
proposalId: rejected.proposalId
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
function provenanceFromProposal(proposal) {
|
|
1106
|
+
return {
|
|
1107
|
+
taskId: proposal.source.taskId,
|
|
1108
|
+
...(proposal.source.decisionId === undefined ? {} : { decisionId: proposal.source.decisionId }),
|
|
1109
|
+
...(proposal.source.milestoneId === undefined ? {} : { milestoneId: proposal.source.milestoneId }),
|
|
1110
|
+
...(proposal.source.commitSha === undefined ? {} : { commitSha: proposal.source.commitSha }),
|
|
1111
|
+
...(proposal.evidenceDigest === undefined ? {} : { evidenceDigest: proposal.evidenceDigest }),
|
|
1112
|
+
fingerprint: proposal.fingerprint
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
/** Replace a Knowledge entry's provenance (used by accept --update). */
|
|
1116
|
+
function withKnowledgeProvenance(project, knowledgeId, provenance, now) {
|
|
1117
|
+
const timestamp = now.toISOString();
|
|
1118
|
+
const knowledge = project.knowledge.map((entry) => (entry.id === knowledgeId
|
|
1119
|
+
? { ...entry, provenance, updatedAt: timestamp }
|
|
1120
|
+
: entry));
|
|
1121
|
+
return validateProject({ ...project, knowledge, updatedAt: timestamp });
|
|
1122
|
+
}
|
|
1123
|
+
function parseAcceptArguments(args, usage) {
|
|
1124
|
+
const positionals = [];
|
|
1125
|
+
let update;
|
|
1126
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
1127
|
+
const value = args[index];
|
|
1128
|
+
if (value === "--update") {
|
|
1129
|
+
if (update !== undefined)
|
|
1130
|
+
throw usageError(`--update may only be provided once. ${usage}`);
|
|
1131
|
+
const next = args[index + 1];
|
|
1132
|
+
if (next === undefined || next.startsWith("--"))
|
|
1133
|
+
throw usageError(`--update is required. ${usage}`);
|
|
1134
|
+
update = requireText(next, "--update");
|
|
1135
|
+
index += 1;
|
|
1136
|
+
}
|
|
1137
|
+
else if (value.startsWith("--")) {
|
|
1138
|
+
throw usageError(`Unknown option: ${value}. ${usage}`);
|
|
1139
|
+
}
|
|
1140
|
+
else {
|
|
1141
|
+
positionals.push(value);
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
if (positionals.length !== 2)
|
|
1145
|
+
throw usageError(usage);
|
|
1146
|
+
return { project: positionals[0], proposal: positionals[1], ...(update === undefined ? {} : { update }) };
|
|
1147
|
+
}
|
|
646
1148
|
function parseAddArguments(args, usage) {
|
|
647
1149
|
const positionals = [];
|
|
648
1150
|
const aliases = [];
|