youmd 0.11.0 → 0.13.0
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/commands/brain.d.ts.map +1 -1
- package/dist/commands/brain.js +77 -28
- package/dist/commands/brain.js.map +1 -1
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +6 -1
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/connect.d.ts +113 -0
- package/dist/commands/connect.d.ts.map +1 -0
- package/dist/commands/connect.js +849 -0
- package/dist/commands/connect.js.map +1 -0
- package/dist/commands/conversations.d.ts +5 -0
- package/dist/commands/conversations.d.ts.map +1 -0
- package/dist/commands/conversations.js +116 -0
- package/dist/commands/conversations.js.map +1 -0
- package/dist/commands/founder.d.ts +16 -0
- package/dist/commands/founder.d.ts.map +1 -0
- package/dist/commands/founder.js +446 -0
- package/dist/commands/founder.js.map +1 -0
- package/dist/commands/handoff.d.ts +5 -0
- package/dist/commands/handoff.d.ts.map +1 -0
- package/dist/commands/handoff.js +432 -0
- package/dist/commands/handoff.js.map +1 -0
- package/dist/commands/orchestrate.d.ts +15 -0
- package/dist/commands/orchestrate.d.ts.map +1 -1
- package/dist/commands/orchestrate.js +294 -0
- package/dist/commands/orchestrate.js.map +1 -1
- package/dist/commands/project.d.ts.map +1 -1
- package/dist/commands/project.js +8 -1
- package/dist/commands/project.js.map +1 -1
- package/dist/commands/reconcile.d.ts +15 -0
- package/dist/commands/reconcile.d.ts.map +1 -0
- package/dist/commands/reconcile.js +260 -0
- package/dist/commands/reconcile.js.map +1 -0
- package/dist/commands/session.d.ts +28 -0
- package/dist/commands/session.d.ts.map +1 -0
- package/dist/commands/session.js +895 -0
- package/dist/commands/session.js.map +1 -0
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +11 -0
- package/dist/commands/sync.js.map +1 -1
- package/dist/commands/tasks.d.ts +20 -0
- package/dist/commands/tasks.d.ts.map +1 -1
- package/dist/commands/tasks.js +90 -19
- package/dist/commands/tasks.js.map +1 -1
- package/dist/generated/capability-registry.d.ts +444 -76
- package/dist/generated/capability-registry.d.ts.map +1 -1
- package/dist/generated/capability-registry.js +497 -76
- package/dist/generated/capability-registry.js.map +1 -1
- package/dist/index.js +122 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/api.d.ts +393 -2
- package/dist/lib/api.d.ts.map +1 -1
- package/dist/lib/api.js +195 -2
- package/dist/lib/api.js.map +1 -1
- package/dist/lib/brain/adapters.d.ts +22 -1
- package/dist/lib/brain/adapters.d.ts.map +1 -1
- package/dist/lib/brain/adapters.js +184 -2
- package/dist/lib/brain/adapters.js.map +1 -1
- package/dist/lib/brain/approval-key-trust.d.ts +29 -0
- package/dist/lib/brain/approval-key-trust.d.ts.map +1 -0
- package/dist/lib/brain/approval-key-trust.js +139 -0
- package/dist/lib/brain/approval-key-trust.js.map +1 -0
- package/dist/lib/brain/content.d.ts +10 -3
- package/dist/lib/brain/content.d.ts.map +1 -1
- package/dist/lib/brain/content.js +133 -30
- package/dist/lib/brain/content.js.map +1 -1
- package/dist/lib/brain/descriptor-validator.d.ts +11 -0
- package/dist/lib/brain/descriptor-validator.d.ts.map +1 -0
- package/dist/lib/brain/descriptor-validator.js +185 -0
- package/dist/lib/brain/descriptor-validator.js.map +1 -0
- package/dist/lib/brain/discovery.d.ts.map +1 -1
- package/dist/lib/brain/discovery.js +77 -50
- package/dist/lib/brain/discovery.js.map +1 -1
- package/dist/lib/brain/index.d.ts +2 -0
- package/dist/lib/brain/index.d.ts.map +1 -1
- package/dist/lib/brain/index.js +14 -4
- package/dist/lib/brain/index.js.map +1 -1
- package/dist/lib/brain/proposal.d.ts +53 -5
- package/dist/lib/brain/proposal.d.ts.map +1 -1
- package/dist/lib/brain/proposal.js +330 -94
- package/dist/lib/brain/proposal.js.map +1 -1
- package/dist/lib/brain/safe-file.d.ts +25 -1
- package/dist/lib/brain/safe-file.d.ts.map +1 -1
- package/dist/lib/brain/safe-file.js +250 -30
- package/dist/lib/brain/safe-file.js.map +1 -1
- package/dist/lib/brain/safety.d.ts.map +1 -1
- package/dist/lib/brain/safety.js +5 -0
- package/dist/lib/brain/safety.js.map +1 -1
- package/dist/lib/brain/staged-apply.d.ts +2 -1
- package/dist/lib/brain/staged-apply.d.ts.map +1 -1
- package/dist/lib/brain/staged-apply.js +6 -3
- package/dist/lib/brain/staged-apply.js.map +1 -1
- package/dist/lib/brain/store.d.ts +3 -0
- package/dist/lib/brain/store.d.ts.map +1 -1
- package/dist/lib/brain/store.js +68 -0
- package/dist/lib/brain/store.js.map +1 -1
- package/dist/lib/brain/sync.d.ts +48 -0
- package/dist/lib/brain/sync.d.ts.map +1 -1
- package/dist/lib/brain/sync.js +102 -0
- package/dist/lib/brain/sync.js.map +1 -1
- package/dist/lib/brain/types.d.ts +23 -0
- package/dist/lib/brain/types.d.ts.map +1 -1
- package/dist/lib/brain/vision.d.ts +35 -0
- package/dist/lib/brain/vision.d.ts.map +1 -0
- package/dist/lib/brain/vision.js +65 -0
- package/dist/lib/brain/vision.js.map +1 -0
- package/dist/lib/businessScaffold.d.ts +42 -0
- package/dist/lib/businessScaffold.d.ts.map +1 -0
- package/dist/lib/businessScaffold.js +150 -0
- package/dist/lib/businessScaffold.js.map +1 -0
- package/dist/lib/codex-device-auth.d.ts +94 -0
- package/dist/lib/codex-device-auth.d.ts.map +1 -0
- package/dist/lib/codex-device-auth.js +295 -0
- package/dist/lib/codex-device-auth.js.map +1 -0
- package/dist/lib/codex-job-control-plane.d.ts +67 -0
- package/dist/lib/codex-job-control-plane.d.ts.map +1 -0
- package/dist/lib/codex-job-control-plane.js +105 -0
- package/dist/lib/codex-job-control-plane.js.map +1 -0
- package/dist/lib/codex-readonly-job.d.ts +76 -0
- package/dist/lib/codex-readonly-job.d.ts.map +1 -0
- package/dist/lib/codex-readonly-job.js +196 -0
- package/dist/lib/codex-readonly-job.js.map +1 -0
- package/dist/lib/config.d.ts +7 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +3 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/connected-access-native.d.ts +111 -0
- package/dist/lib/connected-access-native.d.ts.map +1 -0
- package/dist/lib/connected-access-native.js +403 -0
- package/dist/lib/connected-access-native.js.map +1 -0
- package/dist/lib/connected-context.d.ts +82 -0
- package/dist/lib/connected-context.d.ts.map +1 -0
- package/dist/lib/connected-context.js +610 -0
- package/dist/lib/connected-context.js.map +1 -0
- package/dist/lib/goalRegistryFile.d.ts +169 -0
- package/dist/lib/goalRegistryFile.d.ts.map +1 -0
- package/dist/lib/goalRegistryFile.js +518 -0
- package/dist/lib/goalRegistryFile.js.map +1 -0
- package/dist/lib/goalsFile.d.ts +101 -0
- package/dist/lib/goalsFile.d.ts.map +1 -0
- package/dist/lib/goalsFile.js +413 -0
- package/dist/lib/goalsFile.js.map +1 -0
- package/dist/lib/handoff.d.ts +170 -0
- package/dist/lib/handoff.d.ts.map +1 -0
- package/dist/lib/handoff.js +316 -0
- package/dist/lib/handoff.js.map +1 -0
- package/dist/lib/machine-identity.d.ts +2 -0
- package/dist/lib/machine-identity.d.ts.map +1 -1
- package/dist/lib/machine-identity.js +13 -6
- package/dist/lib/machine-identity.js.map +1 -1
- package/dist/lib/onboarding.d.ts +4 -1
- package/dist/lib/onboarding.d.ts.map +1 -1
- package/dist/lib/onboarding.js +11 -3
- package/dist/lib/onboarding.js.map +1 -1
- package/dist/lib/orchestrator/loop.d.ts.map +1 -1
- package/dist/lib/orchestrator/loop.js +8 -3
- package/dist/lib/orchestrator/loop.js.map +1 -1
- package/dist/lib/orchestrator/run-journal.d.ts +68 -3
- package/dist/lib/orchestrator/run-journal.d.ts.map +1 -1
- package/dist/lib/orchestrator/run-journal.js +391 -37
- package/dist/lib/orchestrator/run-journal.js.map +1 -1
- package/dist/lib/orchestrator/supervisor.d.ts +11 -0
- package/dist/lib/orchestrator/supervisor.d.ts.map +1 -1
- package/dist/lib/orchestrator/supervisor.js +304 -116
- package/dist/lib/orchestrator/supervisor.js.map +1 -1
- package/dist/lib/orchestrator/tools.d.ts.map +1 -1
- package/dist/lib/orchestrator/tools.js +76 -3
- package/dist/lib/orchestrator/tools.js.map +1 -1
- package/dist/lib/redact.d.ts.map +1 -1
- package/dist/lib/redact.js +40 -7
- package/dist/lib/redact.js.map +1 -1
- package/dist/lib/remote-agent-event.d.ts +19 -0
- package/dist/lib/remote-agent-event.d.ts.map +1 -0
- package/dist/lib/remote-agent-event.js +85 -0
- package/dist/lib/remote-agent-event.js.map +1 -0
- package/dist/lib/remote-command.d.ts.map +1 -1
- package/dist/lib/remote-command.js +19 -0
- package/dist/lib/remote-command.js.map +1 -1
- package/dist/lib/remote-executor.d.ts +7 -1
- package/dist/lib/remote-executor.d.ts.map +1 -1
- package/dist/lib/remote-executor.js +391 -0
- package/dist/lib/remote-executor.js.map +1 -1
- package/dist/lib/sessionCheckpoint.d.ts +104 -0
- package/dist/lib/sessionCheckpoint.d.ts.map +1 -0
- package/dist/lib/sessionCheckpoint.js +191 -0
- package/dist/lib/sessionCheckpoint.js.map +1 -0
- package/dist/lib/sessionHandoff.d.ts +47 -0
- package/dist/lib/sessionHandoff.d.ts.map +1 -0
- package/dist/lib/sessionHandoff.js +305 -0
- package/dist/lib/sessionHandoff.js.map +1 -0
- package/dist/lib/stream.d.ts +3 -1
- package/dist/lib/stream.d.ts.map +1 -1
- package/dist/lib/stream.js +8 -3
- package/dist/lib/stream.js.map +1 -1
- package/dist/lib/taskLedgerProposal.d.ts +2 -2
- package/dist/lib/taskLedgerProposal.d.ts.map +1 -1
- package/dist/lib/taskLedgerProposal.js +16 -8
- package/dist/lib/taskLedgerProposal.js.map +1 -1
- package/dist/lib/tasksFile.d.ts +14 -2
- package/dist/lib/tasksFile.d.ts.map +1 -1
- package/dist/lib/tasksFile.js +39 -5
- package/dist/lib/tasksFile.js.map +1 -1
- package/dist/mcp/registry.d.ts +4 -1
- package/dist/mcp/registry.d.ts.map +1 -1
- package/dist/mcp/registry.js +230 -1
- package/dist/mcp/registry.js.map +1 -1
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +18 -9
- package/dist/mcp/server.js.map +1 -1
- package/native/prebuilds/darwin-universal/youmd_safe_open.node +0 -0
- package/package.json +4 -2
- package/scripts/env-vault/README.md +16 -20
- package/scripts/env-vault/backup.sh +22 -57
- package/scripts/env-vault/restore.sh +18 -96
- package/scripts/smoke-safe-open-node.cjs +50 -0
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PROJECT_BRAIN_PROPOSAL_VERSION = exports.PROJECT_BRAIN_PROPOSAL_SCHEMA = exports.PROJECT_BRAIN_OVERRIDE_VERSION = void 0;
|
|
3
|
+
exports.PROJECT_BRAIN_TASK_MUTATION_VERSION = exports.PROJECT_BRAIN_PROPOSAL_VERSION = exports.PROJECT_BRAIN_PROPOSAL_SCHEMA = exports.PROJECT_BRAIN_OVERRIDE_VERSION = void 0;
|
|
4
4
|
exports.validateProjectBrainPatch = validateProjectBrainPatch;
|
|
5
5
|
exports.validateProjectBrainUnifiedDiffPatch = validateProjectBrainUnifiedDiffPatch;
|
|
6
6
|
exports.validateProjectBrainProposal = validateProjectBrainProposal;
|
|
7
|
+
exports.validateProjectBrainAdoptionDescriptor = validateProjectBrainAdoptionDescriptor;
|
|
8
|
+
exports.proposeProjectBrainAdoptions = proposeProjectBrainAdoptions;
|
|
7
9
|
exports.proposeProjectBrainAdoption = proposeProjectBrainAdoption;
|
|
8
10
|
exports.importProjectBrainOverrides = importProjectBrainOverrides;
|
|
9
11
|
const redact_1 = require("../redact");
|
|
10
12
|
const canonical_1 = require("./canonical");
|
|
13
|
+
const descriptor_validator_1 = require("./descriptor-validator");
|
|
11
14
|
exports.PROJECT_BRAIN_OVERRIDE_VERSION = "you-md/project-brain-override/v1";
|
|
12
15
|
exports.PROJECT_BRAIN_PROPOSAL_SCHEMA = "https://you.md/schemas/project-brain-proposal/v1";
|
|
13
16
|
exports.PROJECT_BRAIN_PROPOSAL_VERSION = "you-md/project-brain-proposal/v1";
|
|
17
|
+
exports.PROJECT_BRAIN_TASK_MUTATION_VERSION = "you-md/task-mutation-proposal/v1";
|
|
14
18
|
const PROJECT_REF_PATTERN = /^project:[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/;
|
|
15
19
|
const REVISION_PATTERN = /^sha256:[a-f0-9]{64}$/;
|
|
16
20
|
const OVERRIDE_SOURCE_PATTERN = /^(?:overrides|notes)\/[A-Za-z0-9._-]+(?:\/[A-Za-z0-9._-]+)*\.json$/;
|
|
17
21
|
const ARTIFACT_REF_PATTERN = /^artifact:[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?\/[a-z0-9][a-z0-9._-]{0,95}$/;
|
|
22
|
+
const SAFE_RELATIVE_PATH = /^(?!\/)(?![A-Za-z]:)(?!.*\\)(?!.*\/\/)(?!.*(?:^|\/)(?:\.{1,2})(?:\/|$))(?!.*[\u0000-\u001f\u007f])(?!.*\/$)[^/]+(?:\/[^/]+)*$/;
|
|
18
23
|
const FORBIDDEN_SEGMENTS = new Set(["__proto__", "prototype", "constructor"]);
|
|
19
24
|
const MAX_PATCH_OPERATIONS = 100;
|
|
20
25
|
const MAX_PATCH_BYTES = 256 * 1024;
|
|
@@ -29,6 +34,22 @@ const APPROVAL_BINDINGS = [
|
|
|
29
34
|
"patchFormat",
|
|
30
35
|
"patchDigest",
|
|
31
36
|
];
|
|
37
|
+
const TASK_APPROVAL_BINDINGS = [
|
|
38
|
+
...APPROVAL_BINDINGS,
|
|
39
|
+
"taskMutation.mutationRef",
|
|
40
|
+
];
|
|
41
|
+
const TASK_MUTATION_REF_PATTERN = /^task-mutation:sha256:[a-f0-9]{64}$/;
|
|
42
|
+
const TASK_ID_PATTERN = /^t-[A-Za-z0-9][A-Za-z0-9_-]{0,94}$/;
|
|
43
|
+
const EXACT_ISO_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;
|
|
44
|
+
const TASK_STATUSES = new Set(["proposed", "open", "in_progress", "done", "snoozed", "cancelled"]);
|
|
45
|
+
const TASK_PRIORITIES = new Set(["low", "normal", "high", "urgent"]);
|
|
46
|
+
const TASK_TAG_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
47
|
+
const TASK_TEXT_CONTROL_PATTERN = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/;
|
|
48
|
+
const TASK_CREATE_RICH_KEYS = [
|
|
49
|
+
"requestedDescription", "requestedGoal", "requestedSuccessCriteria", "requestedParentId",
|
|
50
|
+
"requestedDependencyIds", "requestedScope", "requestedBlockers", "requestedClarification",
|
|
51
|
+
"requestedReview", "requestedEvidence", "requestedTags", "requestedVerify", "requestedOwnerLabel",
|
|
52
|
+
];
|
|
32
53
|
function proposalError(sourceRef, code, message, operationIndex) {
|
|
33
54
|
return operationIndex === undefined ? { sourceRef, code, message } : { sourceRef, code, message, operationIndex };
|
|
34
55
|
}
|
|
@@ -353,6 +374,77 @@ function validateProjectBrainUnifiedDiffPatch(raw, sourceRef = "proposal:input")
|
|
|
353
374
|
return { valid: false, errors: [proposalError(sourceRef, "secret_detected", "unified diff contains a secret or private-data pattern")] };
|
|
354
375
|
return { valid: true, errors: [], patch, patchDigest: digest(normalizeJson(patch)) };
|
|
355
376
|
}
|
|
377
|
+
function validTaskMutationText(value, max) {
|
|
378
|
+
return value === undefined || typeof value === "string" && Boolean(value.trim()) && value === value.trim()
|
|
379
|
+
&& value.length <= max && !TASK_TEXT_CONTROL_PATTERN.test(value);
|
|
380
|
+
}
|
|
381
|
+
function validTaskMutationList(value, maxItems, maxText, pattern) {
|
|
382
|
+
return value === undefined || Array.isArray(value) && value.length <= maxItems
|
|
383
|
+
&& new Set(value).size === value.length
|
|
384
|
+
&& value.every((item) => validTaskMutationText(item, maxText)
|
|
385
|
+
&& typeof item === "string" && (!pattern || pattern.test(item)));
|
|
386
|
+
}
|
|
387
|
+
function validateTaskMutation(value, baseContentHash, patchDigest) {
|
|
388
|
+
if (!value || typeof value !== "object" || Array.isArray(value)
|
|
389
|
+
|| !REVISION_PATTERN.test(baseContentHash || "") || !REVISION_PATTERN.test(patchDigest || ""))
|
|
390
|
+
return false;
|
|
391
|
+
const mutation = value;
|
|
392
|
+
const commonValid = mutation.schemaVersion === exports.PROJECT_BRAIN_TASK_MUTATION_VERSION
|
|
393
|
+
&& typeof mutation.mutationRef === "string" && TASK_MUTATION_REF_PATTERN.test(mutation.mutationRef)
|
|
394
|
+
&& typeof mutation.localTaskId === "string" && TASK_ID_PATTERN.test(mutation.localTaskId)
|
|
395
|
+
&& typeof mutation.requestedAt === "string" && EXACT_ISO_PATTERN.test(mutation.requestedAt)
|
|
396
|
+
&& Number.isFinite(Date.parse(mutation.requestedAt));
|
|
397
|
+
if (!commonValid)
|
|
398
|
+
return false;
|
|
399
|
+
const commonKeys = ["schemaVersion", "mutationRef", "operation", "localTaskId", "requestedStatus", "requestedAt"];
|
|
400
|
+
if (mutation.operation === "create") {
|
|
401
|
+
const requiredKeys = [...commonKeys, "requestedTitle", "requestedOwner", "requestedPriority"];
|
|
402
|
+
const allowedKeys = new Set([...requiredKeys, ...TASK_CREATE_RICH_KEYS]);
|
|
403
|
+
if (requiredKeys.some((key) => !Object.prototype.hasOwnProperty.call(mutation, key))
|
|
404
|
+
|| Object.keys(mutation).some((key) => !allowedKeys.has(key))
|
|
405
|
+
|| typeof mutation.requestedTitle !== "string" || !mutation.requestedTitle.trim()
|
|
406
|
+
|| mutation.requestedTitle !== mutation.requestedTitle.trim() || mutation.requestedTitle.length > 240
|
|
407
|
+
|| TASK_TEXT_CONTROL_PATTERN.test(mutation.requestedTitle) || mutation.requestedStatus !== "open"
|
|
408
|
+
|| (mutation.requestedOwner !== "human" && mutation.requestedOwner !== "agent")
|
|
409
|
+
|| typeof mutation.requestedPriority !== "string" || !TASK_PRIORITIES.has(mutation.requestedPriority)
|
|
410
|
+
|| !validTaskMutationText(mutation.requestedDescription, 10000)
|
|
411
|
+
|| !validTaskMutationText(mutation.requestedGoal, 10000)
|
|
412
|
+
|| !validTaskMutationText(mutation.requestedSuccessCriteria, 10000)
|
|
413
|
+
|| !validTaskMutationText(mutation.requestedParentId, 95)
|
|
414
|
+
|| (mutation.requestedParentId !== undefined
|
|
415
|
+
&& (typeof mutation.requestedParentId !== "string" || !TASK_ID_PATTERN.test(mutation.requestedParentId)))
|
|
416
|
+
|| !validTaskMutationList(mutation.requestedDependencyIds, 256, 95, TASK_ID_PATTERN)
|
|
417
|
+
|| !validTaskMutationText(mutation.requestedScope, 4000)
|
|
418
|
+
|| !validTaskMutationList(mutation.requestedBlockers, 128, 2000)
|
|
419
|
+
|| !validTaskMutationText(mutation.requestedClarification, 10000)
|
|
420
|
+
|| !validTaskMutationText(mutation.requestedReview, 10000)
|
|
421
|
+
|| !validTaskMutationList(mutation.requestedEvidence, 256, 4000)
|
|
422
|
+
|| !validTaskMutationList(mutation.requestedTags, 32, 64, TASK_TAG_PATTERN)
|
|
423
|
+
|| !validTaskMutationText(mutation.requestedVerify, 2000)
|
|
424
|
+
|| !validTaskMutationText(mutation.requestedOwnerLabel, 120)
|
|
425
|
+
|| mutation.requestedParentId === mutation.localTaskId
|
|
426
|
+
|| (Array.isArray(mutation.requestedDependencyIds)
|
|
427
|
+
&& mutation.requestedDependencyIds.includes(mutation.localTaskId)))
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
430
|
+
else if (mutation.operation === "update_status") {
|
|
431
|
+
const keys = new Set([...commonKeys, "portfolioTaskId", "expectedPortfolioUpdatedAt"]);
|
|
432
|
+
if (Object.keys(mutation).length !== keys.size || Object.keys(mutation).some((key) => !keys.has(key))
|
|
433
|
+
|| typeof mutation.portfolioTaskId !== "string" || !mutation.portfolioTaskId
|
|
434
|
+
|| mutation.portfolioTaskId.length > 160 || /[\u0000-\u001f\u007f]/.test(mutation.portfolioTaskId)
|
|
435
|
+
|| typeof mutation.expectedPortfolioUpdatedAt !== "number" || !Number.isFinite(mutation.expectedPortfolioUpdatedAt)
|
|
436
|
+
|| typeof mutation.requestedStatus !== "string" || !TASK_STATUSES.has(mutation.requestedStatus))
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
439
|
+
else
|
|
440
|
+
return false;
|
|
441
|
+
const { mutationRef: _mutationRef, ...bindingWithoutRef } = mutation;
|
|
442
|
+
return mutation.mutationRef === `task-mutation:${digest(normalizeJson({
|
|
443
|
+
...bindingWithoutRef,
|
|
444
|
+
baseContentHash,
|
|
445
|
+
patchDigest,
|
|
446
|
+
}))}`;
|
|
447
|
+
}
|
|
356
448
|
/** Validate a transport proposal envelope without approving or applying it. */
|
|
357
449
|
function validateProjectBrainProposal(value) {
|
|
358
450
|
const sourceRef = value && typeof value === "object" && typeof value.proposalRef === "string"
|
|
@@ -371,11 +463,14 @@ function validateProjectBrainProposal(value) {
|
|
|
371
463
|
const proposal = normalized;
|
|
372
464
|
const record = normalized;
|
|
373
465
|
const errors = [];
|
|
466
|
+
const hasTaskMutation = Object.prototype.hasOwnProperty.call(record, "taskMutation");
|
|
374
467
|
const requiredKeys = new Set([
|
|
375
468
|
"$schema", "schemaVersion", "kind", "proposalRef", "proposalType", "projectRef", "machineRef", "workspaceRef",
|
|
376
469
|
"baseRevision", "requestedCapability", "rationale", "status", "target", "patchFormat", "patchDigest", "patch", "diff",
|
|
377
470
|
"approval", "source",
|
|
378
471
|
]);
|
|
472
|
+
if (hasTaskMutation)
|
|
473
|
+
requiredKeys.add("taskMutation");
|
|
379
474
|
if (Object.keys(record).length !== requiredKeys.size || Object.keys(record).some((key) => !requiredKeys.has(key))
|
|
380
475
|
|| proposal.$schema !== exports.PROJECT_BRAIN_PROPOSAL_SCHEMA || proposal.schemaVersion !== exports.PROJECT_BRAIN_PROPOSAL_VERSION
|
|
381
476
|
|| proposal.kind !== "project-brain-proposal" || proposal.status !== "proposed"
|
|
@@ -384,7 +479,7 @@ function validateProjectBrainProposal(value) {
|
|
|
384
479
|
|| typeof proposal.rationale !== "string" || proposal.rationale.trim().length < 3 || proposal.rationale.length > 1000
|
|
385
480
|
|| !proposal.target || proposal.target.humanOwned !== true || !ARTIFACT_REF_PATTERN.test(proposal.target.artifactRef)
|
|
386
481
|
|| !REVISION_PATTERN.test(proposal.target.baseContentHash) || !proposal.source
|
|
387
|
-
|| !/^(?:sha256|heuristic
|
|
482
|
+
|| !/^(?:git|sha256|foldermd|eve|heuristic):[A-Za-z0-9._-]{7,128}$/.test(proposal.source.sourceRevision)) {
|
|
388
483
|
errors.push(proposalError(sourceRef, "invalid_proposal", "proposal envelope or identity is invalid"));
|
|
389
484
|
}
|
|
390
485
|
if (!safeSecretScan(proposal))
|
|
@@ -419,7 +514,7 @@ function validateProjectBrainProposal(value) {
|
|
|
419
514
|
errors.push(proposalError(sourceRef, "invalid_proposal", "proposal patchFormat is unsupported"));
|
|
420
515
|
if (!proposal.approval || proposal.approval.required !== true || proposal.approval.policy !== "fresh_exact_patch"
|
|
421
516
|
|| proposal.approval.reusable !== false || proposal.approval.maxAgeSeconds !== 900
|
|
422
|
-
|| JSON.stringify(proposal.approval.bindingFields) !== JSON.stringify(APPROVAL_BINDINGS)) {
|
|
517
|
+
|| JSON.stringify(proposal.approval.bindingFields) !== JSON.stringify(hasTaskMutation ? TASK_APPROVAL_BINDINGS : APPROVAL_BINDINGS)) {
|
|
423
518
|
errors.push(proposalError(sourceRef, "approval_binding_mismatch", "proposal requires a fresh approval bound to every exact identity and patch field"));
|
|
424
519
|
}
|
|
425
520
|
const { proposalRef: _proposalRef, ...preimage } = proposal;
|
|
@@ -428,8 +523,9 @@ function validateProjectBrainProposal(value) {
|
|
|
428
523
|
}
|
|
429
524
|
const descriptorAdoptionPatchValid = proposal.patchFormat === "rfc6902"
|
|
430
525
|
&& Array.isArray(proposal.patch) && proposal.patch.length === 1
|
|
526
|
+
&& proposal.target?.exists === true
|
|
431
527
|
&& proposal.patch[0]?.path === ""
|
|
432
|
-
&& proposal.patch[0]?.op ===
|
|
528
|
+
&& proposal.patch[0]?.op === "replace"
|
|
433
529
|
&& "value" in proposal.patch[0];
|
|
434
530
|
const typeValid = proposal.proposalType === "descriptor_adoption"
|
|
435
531
|
? proposal.requestedCapability === "brain.descriptor.adopt" && proposal.patchFormat === "rfc6902"
|
|
@@ -443,6 +539,12 @@ function validateProjectBrainProposal(value) {
|
|
|
443
539
|
&& proposal.target.kind === "repository_file" && typeof proposal.target.path === "string" && proposal.target.exists === true;
|
|
444
540
|
if (!typeValid)
|
|
445
541
|
errors.push(proposalError(sourceRef, "invalid_proposal", "proposal type, capability, target, and patch format are inconsistent"));
|
|
542
|
+
if (hasTaskMutation && (!validateTaskMutation(proposal.taskMutation, proposal.target?.baseContentHash, proposal.patchDigest)
|
|
543
|
+
|| proposal.proposalType !== "artifact_update" || proposal.requestedCapability !== "brain.artifact.update"
|
|
544
|
+
|| proposal.patchFormat !== "unified_diff" || proposal.target?.kind !== "repository_file"
|
|
545
|
+
|| proposal.target.path?.split("/").at(-1) !== "tasks.json")) {
|
|
546
|
+
errors.push(proposalError(sourceRef, "invalid_proposal", "task mutation is not an exact, safe repository task binding"));
|
|
547
|
+
}
|
|
446
548
|
return errors.length ? { valid: false, errors } : { valid: true, errors: [], proposal };
|
|
447
549
|
}
|
|
448
550
|
function buildProposal(seed) {
|
|
@@ -470,6 +572,7 @@ function buildProposal(seed) {
|
|
|
470
572
|
function stripFactProvenance(project) {
|
|
471
573
|
const paths = new Set(project.artifacts.map((artifact) => artifact.path));
|
|
472
574
|
return project.artifacts
|
|
575
|
+
.filter((artifact) => artifact.path !== ".you-project" && artifact.path !== ".youmd-project")
|
|
473
576
|
.filter((artifact) => !(artifact.kind === "tasks" && artifact.path.endsWith("/tasks.md")
|
|
474
577
|
&& paths.has(`${artifact.path.slice(0, -"tasks.md".length)}tasks.json`)))
|
|
475
578
|
.map((artifact) => ({
|
|
@@ -479,7 +582,7 @@ function stripFactProvenance(project) {
|
|
|
479
582
|
path: artifact.path,
|
|
480
583
|
owner: artifact.owner,
|
|
481
584
|
authority: artifact.authority,
|
|
482
|
-
writePolicy: "read_only",
|
|
585
|
+
writePolicy: artifact.owner === "repository" && artifact.authority === "canonical" ? "propose" : "read_only",
|
|
483
586
|
provenance: {
|
|
484
587
|
source: artifact.provenance.source,
|
|
485
588
|
sourceRef: artifact.provenance.sourceRef,
|
|
@@ -487,12 +590,53 @@ function stripFactProvenance(project) {
|
|
|
487
590
|
indexedAt: artifact.provenance.indexedAt,
|
|
488
591
|
freshness: artifact.provenance.freshness,
|
|
489
592
|
},
|
|
490
|
-
}))
|
|
593
|
+
}))
|
|
594
|
+
.sort((a, b) => (0, canonical_1.canonicalize)(a).localeCompare((0, canonical_1.canonicalize)(b)));
|
|
491
595
|
}
|
|
492
596
|
const PROJECT_REVISION_FIELDS = [
|
|
493
|
-
"$schema", "schemaVersion", "kind", "projectRef", "slug", "name", "contextRoot",
|
|
494
|
-
"agentRoots", "
|
|
597
|
+
"$schema", "schemaVersion", "kind", "projectRef", "slug", "name", "aliases", "contextRoot",
|
|
598
|
+
"agentRoots", "workspaces", "repositoryBinding", "legacySource", "artifacts", "relationships",
|
|
599
|
+
"writePolicy", "generatedExcludes", "provenance",
|
|
495
600
|
];
|
|
601
|
+
/**
|
|
602
|
+
* Validate the deliberately authority-free descriptor produced by first-time
|
|
603
|
+
* adoption. Adoption may establish catalog ownership, but it must never grant
|
|
604
|
+
* managed or task writes as a side effect of replacing a legacy marker.
|
|
605
|
+
*/
|
|
606
|
+
function validateProjectBrainAdoptionDescriptor(value, projectRef) {
|
|
607
|
+
const validation = (0, descriptor_validator_1.validateProjectBrainDescriptor)(value);
|
|
608
|
+
if (!validation.valid)
|
|
609
|
+
return null;
|
|
610
|
+
let descriptor;
|
|
611
|
+
try {
|
|
612
|
+
descriptor = normalizeJson(value);
|
|
613
|
+
}
|
|
614
|
+
catch {
|
|
615
|
+
return null;
|
|
616
|
+
}
|
|
617
|
+
const record = descriptor;
|
|
618
|
+
const writePolicy = record.writePolicy;
|
|
619
|
+
if (record.projectRef !== projectRef || !writePolicy || typeof writePolicy !== "object" || Array.isArray(writePolicy))
|
|
620
|
+
return null;
|
|
621
|
+
const policy = writePolicy;
|
|
622
|
+
const taskWrite = policy.taskWrite;
|
|
623
|
+
const artifacts = record.artifacts;
|
|
624
|
+
if (policy.default !== "propose" || !Array.isArray(policy.managedPaths) || policy.managedPaths.length !== 0
|
|
625
|
+
|| !taskWrite || typeof taskWrite !== "object" || Array.isArray(taskWrite)
|
|
626
|
+
|| taskWrite.enabled !== false
|
|
627
|
+
|| Object.keys(taskWrite).some((key) => key !== "enabled"))
|
|
628
|
+
return null;
|
|
629
|
+
if (!Array.isArray(artifacts))
|
|
630
|
+
return null;
|
|
631
|
+
for (const artifact of artifacts) {
|
|
632
|
+
if (!artifact || typeof artifact !== "object" || Array.isArray(artifact))
|
|
633
|
+
return null;
|
|
634
|
+
const claim = artifact;
|
|
635
|
+
if (claim.writePolicy !== "propose" && claim.writePolicy !== "read_only")
|
|
636
|
+
return null;
|
|
637
|
+
}
|
|
638
|
+
return descriptor;
|
|
639
|
+
}
|
|
496
640
|
function withoutVolatile(value) {
|
|
497
641
|
if (Array.isArray(value))
|
|
498
642
|
return value.map(withoutVolatile);
|
|
@@ -504,25 +648,50 @@ function withoutVolatile(value) {
|
|
|
504
648
|
result[key] = withoutVolatile(child);
|
|
505
649
|
return result;
|
|
506
650
|
}
|
|
507
|
-
function
|
|
651
|
+
function adoptionBaseRevision(workspaces) {
|
|
652
|
+
if (workspaces.length === 1)
|
|
653
|
+
return workspaces[0].scan.revision.digest;
|
|
654
|
+
return digest(workspaces.map(({ scan }) => ({ workspaceRef: scan.workspaceRef, revision: scan.revision.digest })));
|
|
655
|
+
}
|
|
656
|
+
function adoptionDescriptor(workspaces, name) {
|
|
657
|
+
const { scan, project } = workspaces[0];
|
|
508
658
|
const slug = project.projectRef.slice("project:".length);
|
|
659
|
+
const baseRevision = adoptionBaseRevision(workspaces);
|
|
660
|
+
const repositoryRef = scan.workspaceIdentity.repositoryRef;
|
|
661
|
+
const repositoryBinding = project.repositoryBinding || {
|
|
662
|
+
repositoryRef,
|
|
663
|
+
evidence: { source: "local_workspace", sourceRevision: repositoryRef.replace(/^repository:/, "") },
|
|
664
|
+
};
|
|
665
|
+
const displayName = name?.trim() || project.displayName?.trim()
|
|
666
|
+
|| slug.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
667
|
+
const workspaceBindings = workspaces.map(({ scan: workspace }) => ({
|
|
668
|
+
workspaceRef: workspace.workspaceRef,
|
|
669
|
+
...workspace.workspaceIdentity,
|
|
670
|
+
sourceRevision: workspace.revision.digest,
|
|
671
|
+
})).sort((a, b) => a.workspaceRef.localeCompare(b.workspaceRef));
|
|
509
672
|
const descriptor = {
|
|
510
673
|
$schema: "https://you.md/schemas/project-brain/v1",
|
|
511
674
|
schemaVersion: "you-md/project-brain/v1",
|
|
512
675
|
kind: "project",
|
|
513
676
|
projectRef: project.projectRef,
|
|
514
677
|
slug,
|
|
515
|
-
name:
|
|
678
|
+
name: displayName,
|
|
679
|
+
aliases: [slug],
|
|
516
680
|
contextRoot: project.contextRoot,
|
|
517
|
-
agentRoots: project.artifacts.filter((artifact) => artifact.kind === "agent").map((artifact) => artifact.path),
|
|
681
|
+
agentRoots: [...new Set(project.artifacts.filter((artifact) => artifact.kind === "agent").map((artifact) => artifact.path))].sort(),
|
|
682
|
+
workspaces: workspaceBindings,
|
|
683
|
+
repositoryBinding: normalizeJson(repositoryBinding),
|
|
684
|
+
legacySource: project.management === "legacy-adopted"
|
|
685
|
+
? { path: ".youmd-project", baseContentHash: project.provenance.sourceRevision, preserved: true }
|
|
686
|
+
: null,
|
|
518
687
|
artifacts: stripFactProvenance(project),
|
|
519
688
|
relationships: [],
|
|
520
|
-
writePolicy: { default: "
|
|
689
|
+
writePolicy: { default: "propose", managedPaths: [], taskWrite: { enabled: false } },
|
|
521
690
|
generatedExcludes: [...scan.generatedExcludes],
|
|
522
691
|
provenance: {
|
|
523
|
-
source: project.management === "legacy-adopted" ? "legacy-adoption" : "
|
|
692
|
+
source: project.management === "legacy-adopted" ? "legacy-adoption" : "heuristic-adoption",
|
|
524
693
|
descriptorPath: ".you-project",
|
|
525
|
-
sourceRevision:
|
|
694
|
+
sourceRevision: baseRevision,
|
|
526
695
|
indexedAt: project.provenance.indexedAt,
|
|
527
696
|
freshness: project.provenance.freshness,
|
|
528
697
|
},
|
|
@@ -542,94 +711,161 @@ function adoptionDescriptor(scan, project, name) {
|
|
|
542
711
|
};
|
|
543
712
|
return normalizeJson(descriptor);
|
|
544
713
|
}
|
|
545
|
-
|
|
546
|
-
|
|
714
|
+
function adoptionSemantics(project) {
|
|
715
|
+
return (0, canonical_1.canonicalize)({
|
|
716
|
+
contextRoot: project.contextRoot,
|
|
717
|
+
artifacts: stripFactProvenance(project).map((artifact) => {
|
|
718
|
+
const value = artifact;
|
|
719
|
+
return {
|
|
720
|
+
artifactRef: value.artifactRef,
|
|
721
|
+
subjectRef: value.subjectRef,
|
|
722
|
+
kind: value.kind,
|
|
723
|
+
path: value.path,
|
|
724
|
+
owner: value.owner,
|
|
725
|
+
authority: value.authority,
|
|
726
|
+
writePolicy: value.writePolicy,
|
|
727
|
+
};
|
|
728
|
+
}),
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
function proposalForAdoptionGroup(workspaces, options) {
|
|
547
732
|
const proposals = [];
|
|
548
733
|
const errors = [];
|
|
549
|
-
const
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
if (!
|
|
595
|
-
errors.
|
|
734
|
+
const primary = workspaces[0];
|
|
735
|
+
const { project, scan } = primary;
|
|
736
|
+
if (workspaces.some(({ project: candidate }) => candidate.management === "declared" || candidate.descriptorPath === ".you-project")) {
|
|
737
|
+
return { proposals, errors: [proposalError(project.projectRef, "not_adoptable", "project already has a declared .you-project descriptor")] };
|
|
738
|
+
}
|
|
739
|
+
if (stripFactProvenance(project).length === 0) {
|
|
740
|
+
return { proposals, errors: [proposalError(project.projectRef, "not_adoptable", "project has no attributable non-marker artifacts for descriptor adoption")] };
|
|
741
|
+
}
|
|
742
|
+
if (!SAFE_RELATIVE_PATH.test(project.contextRoot)
|
|
743
|
+
|| workspaces.some(({ project: candidate }) => candidate.artifacts.some((artifact) => !SAFE_RELATIVE_PATH.test(artifact.path)))) {
|
|
744
|
+
return { proposals, errors: [proposalError(project.projectRef, "not_adoptable", "project contains an unsafe descriptor path")] };
|
|
745
|
+
}
|
|
746
|
+
const repositoryRefs = new Set(workspaces.map(({ scan: candidate }) => candidate.workspaceIdentity.repositoryRef));
|
|
747
|
+
const bindingRefs = new Set(workspaces.map(({ project: candidate }) => candidate.repositoryBinding?.repositoryRef).filter(Boolean));
|
|
748
|
+
const githubSubjects = new Set(workspaces.map(({ project: candidate }) => candidate.repositoryBinding?.subjectRef).filter(Boolean));
|
|
749
|
+
if (repositoryRefs.size !== 1 || bindingRefs.size > 1 || githubSubjects.size > 1
|
|
750
|
+
|| (bindingRefs.size === 1 && !bindingRefs.has(scan.workspaceIdentity.repositoryRef))) {
|
|
751
|
+
return { proposals, errors: [proposalError(project.projectRef, "identity_conflict", "logical project maps to conflicting repository evidence; owner review is required")] };
|
|
752
|
+
}
|
|
753
|
+
const semantics = new Set(workspaces.map(({ project: candidate }) => adoptionSemantics(candidate)));
|
|
754
|
+
const legacyRevisions = new Set(workspaces
|
|
755
|
+
.filter(({ project: candidate }) => candidate.management === "legacy-adopted")
|
|
756
|
+
.map(({ project: candidate }) => candidate.provenance.sourceRevision));
|
|
757
|
+
if (semantics.size !== 1 || legacyRevisions.size > 1) {
|
|
758
|
+
return { proposals, errors: [proposalError(project.projectRef, "semantic_review_required", "workspace artifact or legacy-marker claims differ; exact automatic adoption is unsafe")] };
|
|
759
|
+
}
|
|
760
|
+
if (project.management === "legacy-adopted" && !/^sha256:[a-f0-9]{64}$/.test(project.provenance.sourceRevision)) {
|
|
761
|
+
return { proposals, errors: [proposalError(project.projectRef, "not_adoptable", "legacy .youmd-project lacks a verified byte hash; rescan in content mode")] };
|
|
762
|
+
}
|
|
763
|
+
const existingDescriptors = workspaces.flatMap(({ project: candidate }) => candidate.artifacts.filter((artifact) => artifact.path === ".you-project"));
|
|
764
|
+
const existingHashes = new Set(existingDescriptors.map((artifact) => artifact.provenance.sourceRevision));
|
|
765
|
+
if (existingDescriptors.some((artifact) => !/^sha256:[a-f0-9]{64}$/.test(artifact.provenance.sourceRevision))) {
|
|
766
|
+
return { proposals, errors: [proposalError(project.projectRef, "not_adoptable", "existing .you-project lacks a verified content hash; rescan in content mode")] };
|
|
767
|
+
}
|
|
768
|
+
if (existingHashes.size > 1 || (existingDescriptors.length > 0 && existingDescriptors.length !== workspaces.length)) {
|
|
769
|
+
return { proposals, errors: [proposalError(project.projectRef, "semantic_review_required", "workspaces do not share the same exact .you-project base")] };
|
|
770
|
+
}
|
|
771
|
+
if (existingDescriptors.length !== workspaces.length) {
|
|
772
|
+
return { proposals, errors: [proposalError(project.projectRef, "not_adoptable", "descriptor adoption apply is replacement-only; create a valid .you-project through an explicit reviewed repository change before adoption")] };
|
|
773
|
+
}
|
|
774
|
+
const descriptor = adoptionDescriptor(workspaces, options.name);
|
|
775
|
+
const existingDescriptor = existingDescriptors[0];
|
|
776
|
+
const patchResult = (() => {
|
|
777
|
+
const patch = [{ op: "replace", path: "", value: descriptor }];
|
|
778
|
+
const normalized = normalizePatch(patch, project.projectRef);
|
|
779
|
+
if (!normalized.patch)
|
|
780
|
+
return { valid: false, errors: normalized.errors };
|
|
781
|
+
return {
|
|
782
|
+
valid: true,
|
|
783
|
+
errors: [],
|
|
784
|
+
patch: normalized.patch,
|
|
785
|
+
patchDigest: digest(normalized.patch),
|
|
786
|
+
result: descriptor,
|
|
787
|
+
diff: {
|
|
788
|
+
format: "rfc6902",
|
|
789
|
+
beforeDigest: existingDescriptor.provenance.sourceRevision,
|
|
790
|
+
afterDigest: digest(descriptor),
|
|
791
|
+
operations: [{
|
|
792
|
+
index: 0,
|
|
793
|
+
op: "replace",
|
|
794
|
+
path: "",
|
|
795
|
+
before: { present: true },
|
|
796
|
+
after: { present: true, value: descriptor },
|
|
797
|
+
}],
|
|
798
|
+
},
|
|
799
|
+
};
|
|
800
|
+
})();
|
|
801
|
+
if (!patchResult.valid || !patchResult.patch || !patchResult.patchDigest || !patchResult.diff) {
|
|
802
|
+
errors.push(...patchResult.errors);
|
|
803
|
+
return { proposals, errors };
|
|
804
|
+
}
|
|
805
|
+
const slug = project.projectRef.slice("project:".length);
|
|
806
|
+
const baseRevision = adoptionBaseRevision(workspaces);
|
|
807
|
+
proposals.push(buildProposal({
|
|
808
|
+
proposalType: "descriptor_adoption",
|
|
809
|
+
projectRef: project.projectRef,
|
|
810
|
+
machineRef: scan.workspaceIdentity.machineRef,
|
|
811
|
+
workspaceRef: scan.workspaceRef,
|
|
812
|
+
baseRevision,
|
|
813
|
+
requestedCapability: "brain.descriptor.adopt",
|
|
814
|
+
patchFormat: "rfc6902",
|
|
815
|
+
patchDigest: patchResult.patchDigest,
|
|
816
|
+
rationale: options.rationale?.trim() || `Adopt ${project.projectRef} with a reviewable .you-project descriptor.`,
|
|
817
|
+
target: {
|
|
818
|
+
kind: "repository_file",
|
|
819
|
+
artifactRef: `artifact:${slug}/project-descriptor`,
|
|
820
|
+
path: ".you-project",
|
|
821
|
+
owner: "repository",
|
|
822
|
+
authority: "canonical",
|
|
823
|
+
humanOwned: true,
|
|
824
|
+
exists: Boolean(existingDescriptor),
|
|
825
|
+
baseContentHash: existingDescriptor?.provenance.sourceRevision || digest(null),
|
|
826
|
+
},
|
|
827
|
+
patch: patchResult.patch,
|
|
828
|
+
diff: patchResult.diff,
|
|
829
|
+
source: {
|
|
830
|
+
kind: project.management === "legacy-adopted" ? "legacy_marker" : "heuristic_scan",
|
|
831
|
+
sourceRef: project.provenance.sourceRef,
|
|
832
|
+
sourceRevision: project.management === "legacy-adopted" ? project.provenance.sourceRevision : baseRevision,
|
|
833
|
+
...(project.legacyMarkerPath ? { path: project.legacyMarkerPath } : {}),
|
|
834
|
+
},
|
|
835
|
+
}));
|
|
836
|
+
return { proposals, errors };
|
|
837
|
+
}
|
|
838
|
+
/** Generate review-only replacement proposals for an existing invalid `.you-project`. */
|
|
839
|
+
function proposeProjectBrainAdoptions(scans, options = {}) {
|
|
840
|
+
const proposals = [];
|
|
841
|
+
const errors = [];
|
|
842
|
+
const candidates = scans.flatMap((scan) => scan.projects.map((project) => ({ scan, project })))
|
|
843
|
+
.filter(({ project }) => !options.projectRef || project.projectRef === options.projectRef)
|
|
844
|
+
.sort((a, b) => a.project.projectRef.localeCompare(b.project.projectRef)
|
|
845
|
+
|| a.scan.workspaceRef.localeCompare(b.scan.workspaceRef)
|
|
846
|
+
|| a.scan.revision.digest.localeCompare(b.scan.revision.digest));
|
|
847
|
+
if (options.projectRef && candidates.length === 0) {
|
|
848
|
+
return { proposals, errors: [proposalError(options.projectRef, "unknown_project", "project is not present in the exact scans")] };
|
|
849
|
+
}
|
|
850
|
+
const grouped = new Map();
|
|
851
|
+
for (const candidate of candidates)
|
|
852
|
+
grouped.set(candidate.project.projectRef, [...(grouped.get(candidate.project.projectRef) || []), candidate]);
|
|
853
|
+
for (const [projectRef, workspaces] of [...grouped].sort(([a], [b]) => a.localeCompare(b))) {
|
|
854
|
+
if (options.name && !options.projectRef && grouped.size > 1) {
|
|
855
|
+
errors.push(proposalError(projectRef, "semantic_review_required", "--name requires a selected project when adopting multiple logical projects"));
|
|
596
856
|
continue;
|
|
597
857
|
}
|
|
598
|
-
const
|
|
599
|
-
proposals.push(
|
|
600
|
-
|
|
601
|
-
projectRef: project.projectRef,
|
|
602
|
-
machineRef: scan.workspaceIdentity.machineRef,
|
|
603
|
-
workspaceRef: scan.workspaceRef,
|
|
604
|
-
baseRevision: scan.revision.digest,
|
|
605
|
-
requestedCapability: "brain.descriptor.adopt",
|
|
606
|
-
patchFormat: "rfc6902",
|
|
607
|
-
patchDigest: patchResult.patchDigest,
|
|
608
|
-
rationale: options.rationale?.trim() || `Adopt ${project.projectRef} with a reviewable .you-project descriptor.`,
|
|
609
|
-
target: {
|
|
610
|
-
kind: "repository_file",
|
|
611
|
-
artifactRef: `artifact:${slug}/project-descriptor`,
|
|
612
|
-
path: ".you-project",
|
|
613
|
-
owner: "repository",
|
|
614
|
-
authority: "canonical",
|
|
615
|
-
humanOwned: true,
|
|
616
|
-
exists: Boolean(existingDescriptor),
|
|
617
|
-
baseContentHash: existingDescriptor?.provenance.sourceRevision || digest(null),
|
|
618
|
-
},
|
|
619
|
-
patch: patchResult.patch,
|
|
620
|
-
diff: patchResult.diff,
|
|
621
|
-
source: {
|
|
622
|
-
kind: project.management === "legacy-adopted" ? "legacy_marker" : "heuristic_scan",
|
|
623
|
-
sourceRef: project.provenance.sourceRef,
|
|
624
|
-
sourceRevision: project.provenance.sourceRevision,
|
|
625
|
-
...(project.legacyMarkerPath ? { path: project.legacyMarkerPath } : {}),
|
|
626
|
-
},
|
|
627
|
-
}));
|
|
858
|
+
const generated = proposalForAdoptionGroup(workspaces, options);
|
|
859
|
+
proposals.push(...generated.proposals);
|
|
860
|
+
errors.push(...generated.errors);
|
|
628
861
|
}
|
|
629
|
-
proposals.sort((a, b) => a.proposalRef.localeCompare(b.proposalRef));
|
|
862
|
+
proposals.sort((a, b) => a.projectRef.localeCompare(b.projectRef) || a.proposalRef.localeCompare(b.proposalRef));
|
|
630
863
|
errors.sort((a, b) => a.sourceRef.localeCompare(b.sourceRef) || a.code.localeCompare(b.code));
|
|
631
864
|
return { proposals, errors };
|
|
632
865
|
}
|
|
866
|
+
function proposeProjectBrainAdoption(scan, options = {}) {
|
|
867
|
+
return proposeProjectBrainAdoptions([scan], options);
|
|
868
|
+
}
|
|
633
869
|
function parseOverride(value) {
|
|
634
870
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
635
871
|
return null;
|