youmd 0.10.2 → 0.12.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 +91 -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/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/doctor.d.ts +1 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +16 -1
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/founder.d.ts +13 -0
- package/dist/commands/founder.d.ts.map +1 -0
- package/dist/commands/founder.js +189 -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/stack.d.ts +2 -0
- package/dist/commands/stack.d.ts.map +1 -1
- package/dist/commands/stack.js +26 -0
- package/dist/commands/stack.js.map +1 -1
- 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 +57 -0
- package/dist/commands/tasks.d.ts.map +1 -1
- package/dist/commands/tasks.js +236 -31
- package/dist/commands/tasks.js.map +1 -1
- package/dist/generated/capability-registry.d.ts +311 -71
- package/dist/generated/capability-registry.d.ts.map +1 -1
- package/dist/generated/capability-registry.js +346 -71
- package/dist/generated/capability-registry.js.map +1 -1
- package/dist/index.js +113 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/api.d.ts +362 -1
- package/dist/lib/api.d.ts.map +1 -1
- package/dist/lib/api.js +171 -0
- package/dist/lib/api.js.map +1 -1
- package/dist/lib/brain/adapters.d.ts +20 -1
- package/dist/lib/brain/adapters.d.ts.map +1 -1
- package/dist/lib/brain/adapters.js +113 -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/cache.d.ts +2 -2
- package/dist/lib/brain/cache.d.ts.map +1 -1
- package/dist/lib/brain/cache.js +4 -4
- package/dist/lib/brain/cache.js.map +1 -1
- package/dist/lib/brain/content.d.ts +56 -0
- package/dist/lib/brain/content.d.ts.map +1 -0
- package/dist/lib/brain/content.js +362 -0
- package/dist/lib/brain/content.js.map +1 -0
- 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 +103 -60
- 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 +8 -2
- 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 +341 -64
- 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/safe-open.d.ts +2 -0
- package/dist/lib/brain/safe-open.d.ts.map +1 -0
- package/dist/lib/brain/safe-open.js +77 -0
- package/dist/lib/brain/safe-open.js.map +1 -0
- 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 +87 -0
- package/dist/lib/brain/sync.d.ts.map +1 -0
- package/dist/lib/brain/sync.js +238 -0
- package/dist/lib/brain/sync.js.map +1 -0
- package/dist/lib/brain/types.d.ts +23 -0
- package/dist/lib/brain/types.d.ts.map +1 -1
- 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/daemon.js +1 -1
- package/dist/lib/daemon.js.map +1 -1
- 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 +7 -0
- package/dist/lib/orchestrator/loop.d.ts.map +1 -1
- package/dist/lib/orchestrator/loop.js +53 -0
- package/dist/lib/orchestrator/loop.js.map +1 -1
- package/dist/lib/orchestrator/run-journal.d.ts +178 -0
- package/dist/lib/orchestrator/run-journal.d.ts.map +1 -0
- package/dist/lib/orchestrator/run-journal.js +912 -0
- package/dist/lib/orchestrator/run-journal.js.map +1 -0
- 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 +2 -0
- package/dist/lib/orchestrator/tools.d.ts.map +1 -1
- package/dist/lib/orchestrator/tools.js +214 -1
- package/dist/lib/orchestrator/tools.js.map +1 -1
- package/dist/lib/redact.d.ts.map +1 -1
- package/dist/lib/redact.js +31 -6
- 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 +102 -0
- package/dist/lib/taskLedgerProposal.d.ts.map +1 -0
- package/dist/lib/taskLedgerProposal.js +408 -0
- package/dist/lib/taskLedgerProposal.js.map +1 -0
- package/dist/lib/tasksFile.d.ts +23 -2
- package/dist/lib/tasksFile.d.ts.map +1 -1
- package/dist/lib/tasksFile.js +122 -10
- package/dist/lib/tasksFile.js.map +1 -1
- package/dist/mcp/registry.d.ts.map +1 -1
- package/dist/mcp/registry.js +110 -0
- package/dist/mcp/registry.js.map +1 -1
- package/native/prebuilds/darwin-universal/youmd_safe_open.node +0 -0
- package/native/safe-open/youmd_safe_open.c +224 -0
- package/package.json +11 -3
- package/scripts/build-safe-open.mjs +63 -0
- package/scripts/skillstack-sync/com.you.context-sync.plist +1 -1
- package/scripts/skillstack-sync/com.you.identity-sync.plist +1 -1
- package/scripts/skillstack-sync/com.you.orchestrator-watch.plist +1 -1
- package/scripts/skillstack-sync/com.you.realtime-sync.plist +7 -1
- package/scripts/skillstack-sync/com.you.skillstack-sync.plist +1 -1
- package/scripts/skillstack-sync/context-sync.sh +19 -7
- package/scripts/skillstack-sync/install-daemons-linux.sh +6 -0
- 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,16 +514,23 @@ 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;
|
|
426
521
|
if (proposal.proposalRef !== `proposal:brain/${(0, canonical_1.sha256)((0, canonical_1.canonicalize)(preimage))}`) {
|
|
427
522
|
errors.push(proposalError(sourceRef, "proposal_ref_mismatch", "proposalRef does not match the canonical proposal envelope"));
|
|
428
523
|
}
|
|
524
|
+
const descriptorAdoptionPatchValid = proposal.patchFormat === "rfc6902"
|
|
525
|
+
&& Array.isArray(proposal.patch) && proposal.patch.length === 1
|
|
526
|
+
&& proposal.target?.exists === true
|
|
527
|
+
&& proposal.patch[0]?.path === ""
|
|
528
|
+
&& proposal.patch[0]?.op === "replace"
|
|
529
|
+
&& "value" in proposal.patch[0];
|
|
429
530
|
const typeValid = proposal.proposalType === "descriptor_adoption"
|
|
430
531
|
? proposal.requestedCapability === "brain.descriptor.adopt" && proposal.patchFormat === "rfc6902"
|
|
431
|
-
&& proposal.target.kind === "repository_file" && proposal.target.path === ".you-project"
|
|
532
|
+
&& proposal.target.kind === "repository_file" && proposal.target.path === ".you-project"
|
|
533
|
+
&& descriptorAdoptionPatchValid
|
|
432
534
|
: proposal.proposalType === "personal_override"
|
|
433
535
|
? proposal.requestedCapability === "brain.override.propose" && proposal.patchFormat === "rfc6902"
|
|
434
536
|
&& proposal.target.kind === "project_snapshot" && proposal.target.path === undefined && proposal.target.exists === true
|
|
@@ -437,6 +539,12 @@ function validateProjectBrainProposal(value) {
|
|
|
437
539
|
&& proposal.target.kind === "repository_file" && typeof proposal.target.path === "string" && proposal.target.exists === true;
|
|
438
540
|
if (!typeValid)
|
|
439
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
|
+
}
|
|
440
548
|
return errors.length ? { valid: false, errors } : { valid: true, errors: [], proposal };
|
|
441
549
|
}
|
|
442
550
|
function buildProposal(seed) {
|
|
@@ -462,14 +570,19 @@ function buildProposal(seed) {
|
|
|
462
570
|
return { ...preimage, proposalRef: `proposal:brain/${(0, canonical_1.sha256)((0, canonical_1.canonicalize)(preimage))}` };
|
|
463
571
|
}
|
|
464
572
|
function stripFactProvenance(project) {
|
|
465
|
-
|
|
573
|
+
const paths = new Set(project.artifacts.map((artifact) => artifact.path));
|
|
574
|
+
return project.artifacts
|
|
575
|
+
.filter((artifact) => artifact.path !== ".you-project" && artifact.path !== ".youmd-project")
|
|
576
|
+
.filter((artifact) => !(artifact.kind === "tasks" && artifact.path.endsWith("/tasks.md")
|
|
577
|
+
&& paths.has(`${artifact.path.slice(0, -"tasks.md".length)}tasks.json`)))
|
|
578
|
+
.map((artifact) => ({
|
|
466
579
|
artifactRef: artifact.artifactRef,
|
|
467
580
|
subjectRef: artifact.subjectRef,
|
|
468
581
|
kind: artifact.kind,
|
|
469
582
|
path: artifact.path,
|
|
470
583
|
owner: artifact.owner,
|
|
471
584
|
authority: artifact.authority,
|
|
472
|
-
writePolicy: "read_only",
|
|
585
|
+
writePolicy: artifact.owner === "repository" && artifact.authority === "canonical" ? "propose" : "read_only",
|
|
473
586
|
provenance: {
|
|
474
587
|
source: artifact.provenance.source,
|
|
475
588
|
sourceRef: artifact.provenance.sourceRef,
|
|
@@ -477,12 +590,53 @@ function stripFactProvenance(project) {
|
|
|
477
590
|
indexedAt: artifact.provenance.indexedAt,
|
|
478
591
|
freshness: artifact.provenance.freshness,
|
|
479
592
|
},
|
|
480
|
-
}))
|
|
593
|
+
}))
|
|
594
|
+
.sort((a, b) => (0, canonical_1.canonicalize)(a).localeCompare((0, canonical_1.canonicalize)(b)));
|
|
481
595
|
}
|
|
482
596
|
const PROJECT_REVISION_FIELDS = [
|
|
483
|
-
"$schema", "schemaVersion", "kind", "projectRef", "slug", "name", "contextRoot",
|
|
484
|
-
"agentRoots", "
|
|
597
|
+
"$schema", "schemaVersion", "kind", "projectRef", "slug", "name", "aliases", "contextRoot",
|
|
598
|
+
"agentRoots", "workspaces", "repositoryBinding", "legacySource", "artifacts", "relationships",
|
|
599
|
+
"writePolicy", "generatedExcludes", "provenance",
|
|
485
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
|
+
}
|
|
486
640
|
function withoutVolatile(value) {
|
|
487
641
|
if (Array.isArray(value))
|
|
488
642
|
return value.map(withoutVolatile);
|
|
@@ -494,25 +648,50 @@ function withoutVolatile(value) {
|
|
|
494
648
|
result[key] = withoutVolatile(child);
|
|
495
649
|
return result;
|
|
496
650
|
}
|
|
497
|
-
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];
|
|
498
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));
|
|
499
672
|
const descriptor = {
|
|
500
673
|
$schema: "https://you.md/schemas/project-brain/v1",
|
|
501
674
|
schemaVersion: "you-md/project-brain/v1",
|
|
502
675
|
kind: "project",
|
|
503
676
|
projectRef: project.projectRef,
|
|
504
677
|
slug,
|
|
505
|
-
name:
|
|
678
|
+
name: displayName,
|
|
679
|
+
aliases: [slug],
|
|
506
680
|
contextRoot: project.contextRoot,
|
|
507
|
-
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,
|
|
508
687
|
artifacts: stripFactProvenance(project),
|
|
509
688
|
relationships: [],
|
|
510
|
-
writePolicy: { default: "
|
|
689
|
+
writePolicy: { default: "propose", managedPaths: [], taskWrite: { enabled: false } },
|
|
511
690
|
generatedExcludes: [...scan.generatedExcludes],
|
|
512
691
|
provenance: {
|
|
513
|
-
source: project.management === "legacy-adopted" ? "legacy-adoption" : "
|
|
692
|
+
source: project.management === "legacy-adopted" ? "legacy-adoption" : "heuristic-adoption",
|
|
514
693
|
descriptorPath: ".you-project",
|
|
515
|
-
sourceRevision:
|
|
694
|
+
sourceRevision: baseRevision,
|
|
516
695
|
indexedAt: project.provenance.indexedAt,
|
|
517
696
|
freshness: project.provenance.freshness,
|
|
518
697
|
},
|
|
@@ -532,63 +711,161 @@ function adoptionDescriptor(scan, project, name) {
|
|
|
532
711
|
};
|
|
533
712
|
return normalizeJson(descriptor);
|
|
534
713
|
}
|
|
535
|
-
|
|
536
|
-
|
|
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) {
|
|
537
732
|
const proposals = [];
|
|
538
733
|
const errors = [];
|
|
539
|
-
const
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
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"));
|
|
555
856
|
continue;
|
|
556
857
|
}
|
|
557
|
-
const
|
|
558
|
-
proposals.push(
|
|
559
|
-
|
|
560
|
-
projectRef: project.projectRef,
|
|
561
|
-
machineRef: scan.workspaceIdentity.machineRef,
|
|
562
|
-
workspaceRef: scan.workspaceRef,
|
|
563
|
-
baseRevision: scan.revision.digest,
|
|
564
|
-
requestedCapability: "brain.descriptor.adopt",
|
|
565
|
-
patchFormat: "rfc6902",
|
|
566
|
-
patchDigest: patchResult.patchDigest,
|
|
567
|
-
rationale: options.rationale?.trim() || `Adopt ${project.projectRef} with a reviewable .you-project descriptor.`,
|
|
568
|
-
target: {
|
|
569
|
-
kind: "repository_file",
|
|
570
|
-
artifactRef: `artifact:${slug}/project-descriptor`,
|
|
571
|
-
path: ".you-project",
|
|
572
|
-
owner: "repository",
|
|
573
|
-
authority: "canonical",
|
|
574
|
-
humanOwned: true,
|
|
575
|
-
exists: false,
|
|
576
|
-
baseContentHash: digest(null),
|
|
577
|
-
},
|
|
578
|
-
patch: patchResult.patch,
|
|
579
|
-
diff: patchResult.diff,
|
|
580
|
-
source: {
|
|
581
|
-
kind: project.management === "legacy-adopted" ? "legacy_marker" : "heuristic_scan",
|
|
582
|
-
sourceRef: project.provenance.sourceRef,
|
|
583
|
-
sourceRevision: project.provenance.sourceRevision,
|
|
584
|
-
...(project.legacyMarkerPath ? { path: project.legacyMarkerPath } : {}),
|
|
585
|
-
},
|
|
586
|
-
}));
|
|
858
|
+
const generated = proposalForAdoptionGroup(workspaces, options);
|
|
859
|
+
proposals.push(...generated.proposals);
|
|
860
|
+
errors.push(...generated.errors);
|
|
587
861
|
}
|
|
588
|
-
proposals.sort((a, b) => a.proposalRef.localeCompare(b.proposalRef));
|
|
862
|
+
proposals.sort((a, b) => a.projectRef.localeCompare(b.projectRef) || a.proposalRef.localeCompare(b.proposalRef));
|
|
589
863
|
errors.sort((a, b) => a.sourceRef.localeCompare(b.sourceRef) || a.code.localeCompare(b.code));
|
|
590
864
|
return { proposals, errors };
|
|
591
865
|
}
|
|
866
|
+
function proposeProjectBrainAdoption(scan, options = {}) {
|
|
867
|
+
return proposeProjectBrainAdoptions([scan], options);
|
|
868
|
+
}
|
|
592
869
|
function parseOverride(value) {
|
|
593
870
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
594
871
|
return null;
|