@tiinex/core 0.27.0 → 0.29.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/package.json +5 -5
- package/src/schemas/creation.contracts.js +6 -2
- package/src/schemas/creation.representation.js +2 -1
- package/src/tooling/portable/adapters/cli/cli.command-input.js +1 -0
- package/src/tooling/portable/draft/draft.exact.js +6 -2
- package/src/tooling/portable/editor/authoring.parent.js +15 -2
- package/src/tooling/portable/materialization/epistemic.plan.js +21 -7
- package/src/tooling/portable/workspace/workspace.initialize.js +1 -1
- package/src/transitions/record.transitions.js +20 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiinex/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.0",
|
|
4
4
|
"description": "Shared host-neutral Tiinex implementation core for artifacts, schemas, validation, lineage, grounding, Handoffs, provenance and deterministic workflows.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": true,
|
|
@@ -173,12 +173,12 @@
|
|
|
173
173
|
"type": "git",
|
|
174
174
|
"url": "git+https://github.com/Tiinex/core.git"
|
|
175
175
|
},
|
|
176
|
-
"gitHead": "
|
|
176
|
+
"gitHead": "d2ab9eeaaf1a0c0d13e2d7a5f9de4d010cf5e5bb",
|
|
177
177
|
"tiinexRelease": {
|
|
178
178
|
"policy": "tiinex.master-npm-release.v1",
|
|
179
|
-
"sourceCommit": "
|
|
180
|
-
"sourceTree": "
|
|
179
|
+
"sourceCommit": "d2ab9eeaaf1a0c0d13e2d7a5f9de4d010cf5e5bb",
|
|
180
|
+
"sourceTree": "50ae220f210ec6551825735fb7d9ba8f7956edc5",
|
|
181
181
|
"repository": "Tiinex/core",
|
|
182
|
-
"previousVersion": "0.
|
|
182
|
+
"previousVersion": "0.28.0"
|
|
183
183
|
}
|
|
184
184
|
}
|
|
@@ -173,7 +173,10 @@ export function validateArtifactCreationResult(draft = {}, parentRecord = {}, op
|
|
|
173
173
|
|
|
174
174
|
if (rootCreation) findings.push(...validateRootCreationRepresentation(draft.markdown || '', contract));
|
|
175
175
|
else if (parentExpected) {
|
|
176
|
-
const
|
|
176
|
+
const recoveryMode = String(parentRecord?.recoveryMode || parentRecord?.parentRecoveryMode || '').trim();
|
|
177
|
+
const relativeReference = recoveryMode === 'workspace-qualified'
|
|
178
|
+
? String(parentRecord.relativeReference || parentRecord.path || '')
|
|
179
|
+
: relativePath(dirname(options.childPath || draft.path || ''), parentRecord.path || '');
|
|
177
180
|
const parentIntegrityTarget = qualifiedParentIntegrityTarget(parentRecord, relativeReference);
|
|
178
181
|
const representation = qualifyContinuationCreationRepresentation(draft.markdown || '', contract, parentRecord, { relativeReference, parentIntegrityTarget });
|
|
179
182
|
findings.push(...(representation.findings || []).map((message, index) => error(`creation.continuation-representation.${index + 1}`, message)));
|
|
@@ -221,7 +224,8 @@ function qualifiedParentIntegrityTarget(parentRecord = {}, relativeReference = '
|
|
|
221
224
|
const published = parentRecord?.publishedReference || parentRecord?.browseGitReference || parentRecord?.browseGit || null;
|
|
222
225
|
const target = typeof published === 'string' ? '' : String(published?.target || published?.url || '');
|
|
223
226
|
const state = typeof published === 'string' ? 'unresolved' : String(published?.state || published?.resolutionState || 'unresolved');
|
|
224
|
-
const
|
|
227
|
+
const declaredRecoveryMode = String(parentRecord?.recoveryMode || parentRecord?.parentRecoveryMode || '').trim();
|
|
228
|
+
const recoveryMode = declaredRecoveryMode === 'external-versioned' ? 'external-versioned' : declaredRecoveryMode === 'workspace-qualified' ? 'workspace-qualified' : 'local-relative';
|
|
225
229
|
if (recoveryMode === 'external-versioned') return state === 'qualified' && target ? target : '';
|
|
226
230
|
return state === 'qualified' && target ? target : String(relativeReference || '');
|
|
227
231
|
}
|
|
@@ -217,7 +217,8 @@ export function qualifyContinuationCreationRepresentation(markdown = '', contrac
|
|
|
217
217
|
const publishedTarget = typeof publishedReference === 'string' ? '' : String(publishedReference?.target || publishedReference?.url || '');
|
|
218
218
|
const publishedState = typeof publishedReference === 'string' ? 'unresolved' : String(publishedReference?.state || publishedReference?.resolutionState || 'unresolved');
|
|
219
219
|
const publishedQualified = Boolean(publishedTarget && publishedState === 'qualified');
|
|
220
|
-
const
|
|
220
|
+
const declaredRecoveryMode = String(parentRecord?.recoveryMode || parentRecord?.parentRecoveryMode || '').trim();
|
|
221
|
+
const recoveryMode = declaredRecoveryMode === 'external-versioned' ? 'external-versioned' : declaredRecoveryMode === 'workspace-qualified' ? 'workspace-qualified' : 'local-relative';
|
|
221
222
|
if (recoveryMode === 'external-versioned') {
|
|
222
223
|
if (!publishedQualified) findings.push('External Parent recovery requires one qualified version-stable published representation.');
|
|
223
224
|
if (origins.length !== 1 || relative.length !== 0 || browse.length !== 1) findings.push('External Parent Origin must contain exactly one [browse + git](...) entry and must not fabricate [relative](...).');
|
|
@@ -182,6 +182,7 @@ export async function commandInput(parsed, runtime = {}) {
|
|
|
182
182
|
options: {}
|
|
183
183
|
};
|
|
184
184
|
if(parsed.command==='project-workspace-landing')return land(flags,material,readOptionalJson,splitFlag);
|
|
185
|
+
if (parsed.command === 'project-authoring-parent') return { input: { ...material, reference: flags.reference || '' }, options: {} };
|
|
185
186
|
const operatorBridgeInput = await prepareOperatorBridgeCliInput(parsed.command, material, flags, readOptionalJson);
|
|
186
187
|
if (operatorBridgeInput) return operatorBridgeInput;
|
|
187
188
|
if (parsed.command === 'project-editor-assistance') return prepareEditorAssistanceCliInput(material, flags);
|
|
@@ -16,6 +16,7 @@ export function normalizePortableParentRecord(parent = {}) {
|
|
|
16
16
|
boundary: String(parent.boundary || parent.source?.boundary || ''),
|
|
17
17
|
sourceMode: String(parent.sourceMode || ''),
|
|
18
18
|
recoveryMode: String(parent.recoveryMode || parent.parentRecoveryMode || ''),
|
|
19
|
+
relativeReference: String(parent.relativeReference || ''),
|
|
19
20
|
source: parent.source || null,
|
|
20
21
|
markdown: String(parent.markdown || ''),
|
|
21
22
|
integrity: parent.integrity || null,
|
|
@@ -108,11 +109,14 @@ export function qualifyPortableRenderedParentRepresentation(markdown = '', paren
|
|
|
108
109
|
const observed = parsed.envelope?.parent || {};
|
|
109
110
|
const hasObserved = Boolean(observed.schema?.id || observed.trace || observed.origin || observed.createdAt || observed.boundary || observed.originEntries?.length);
|
|
110
111
|
if (rootCreation) return Object.freeze({ state: hasObserved ? 'invalid' : 'qualified', reason: hasObserved ? 'exact-result-parent-unexpected' : '' });
|
|
111
|
-
const relative = relativePath(dirname(childPath), parent.path);
|
|
112
112
|
const published = parent.publishedReference || {};
|
|
113
113
|
const publishedTarget = String(published.target || '');
|
|
114
114
|
const publishedQualified = Boolean(publishedTarget && published.state === 'qualified');
|
|
115
|
-
const
|
|
115
|
+
const declaredRecoveryMode = String(parent.recoveryMode || '').trim();
|
|
116
|
+
const recoveryMode = declaredRecoveryMode === 'external-versioned' ? 'external-versioned' : declaredRecoveryMode === 'workspace-qualified' ? 'workspace-qualified' : 'local-relative';
|
|
117
|
+
const relative = recoveryMode === 'workspace-qualified'
|
|
118
|
+
? String(parent.relativeReference || parent.path || '')
|
|
119
|
+
: relativePath(dirname(childPath), parent.path);
|
|
116
120
|
const schemaTarget = String(parent.schemaReferenceAuthority?.preferredTarget || '');
|
|
117
121
|
const origins = observed.originEntries || [];
|
|
118
122
|
const createdAtLines = renderedParentFieldLines(markdown, 'Created At');
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { parseArtifactMarkdown } from '../../../artifacts/artifact.parse.js';
|
|
2
2
|
import { auditPortableRecord } from '../audit/audit.capability.js';
|
|
3
|
+
import { classifyParentRecoveryReference } from '../../../lineage/parentRecoveryReference.js';
|
|
4
|
+
import { buildArtifactCreationContract } from '../../../schemas/creation.contracts.js';
|
|
3
5
|
|
|
4
6
|
export const PORTABLE_AUTHORING_PARENT_SCHEMA_ID = 'tiinex.portable.authoring-parent.v1';
|
|
5
7
|
|
|
@@ -15,13 +17,24 @@ export function projectPortableAuthoringParent(input = {}) {
|
|
|
15
17
|
const schemaId = String(parsed.envelope?.current?.schema?.id || audit.schemaId || '');
|
|
16
18
|
const schemaTarget = String(parsed.envelope?.current?.schema?.target || '');
|
|
17
19
|
const createdAt = String(parsed.envelope?.current?.createdAt || audit.artifact?.createdAt || '');
|
|
20
|
+
const explicitReference = String(input.reference || '').trim();
|
|
21
|
+
const projectedPath = explicitReference || String(record.path || record.id || '');
|
|
22
|
+
const referenceClassification = classifyParentRecoveryReference(projectedPath);
|
|
23
|
+
if (referenceClassification.kind === 'malformed-workspace-qualified') return freeze({ schema: PORTABLE_AUTHORING_PARENT_SCHEMA_ID, status: 'blocked', parentRecord: null, findings: [{ severity: 'error', code: 'portable.authoring-parent.reference.malformed', message: 'Selected Parent reference is malformed and cannot be used for native authoring.' }], operationBoundary: boundary() });
|
|
24
|
+
const recoveryMode = referenceClassification.kind === 'workspace-qualified' ? 'workspace-qualified' : 'local-relative';
|
|
25
|
+
const canonicalCurrent = buildArtifactCreationContract({ schemaId, transitionType: 'continue-from-record' })?.schemaReferences?.current || null;
|
|
26
|
+
const canonicalTargets = new Set([...(canonicalCurrent?.exactTargets || []), String(canonicalCurrent?.preferredTarget || '')].filter(Boolean));
|
|
27
|
+
const schemaReferenceQualified = Boolean(schemaTarget && canonicalCurrent?.resolutionState === 'qualified' && canonicalTargets.has(schemaTarget));
|
|
28
|
+
const schemaReferenceAuthority = schemaReferenceQualified
|
|
29
|
+
? { ...canonicalCurrent, preferredTarget: schemaTarget, resolutionState: 'qualified', resolutionEvidence: { ...(canonicalCurrent?.resolutionEvidence || {}), basis: 'declared-parent-target-exact-canonical-match' } }
|
|
30
|
+
: { schemaId, preferredTarget: schemaTarget, exactTargets: schemaTarget ? [schemaTarget] : [], resolutionState: 'unresolved', evidence: { basis: 'declared-current-schema-reference-only' } };
|
|
18
31
|
return freeze({
|
|
19
32
|
schema: PORTABLE_AUTHORING_PARENT_SCHEMA_ID,
|
|
20
33
|
status: 'ready',
|
|
21
34
|
parentRecord: {
|
|
22
|
-
id:
|
|
35
|
+
id: projectedPath, path: projectedPath, schemaId, currentSchemaId: schemaId, currentCreatedAt: createdAt, createdAt, recoveryMode, relativeReference: recoveryMode === 'workspace-qualified' ? projectedPath : '',
|
|
23
36
|
markdown: record.markdown, sourceMode: String(record.sourceMode || 'portable-node-local'),
|
|
24
|
-
schemaReferenceAuthority
|
|
37
|
+
schemaReferenceAuthority
|
|
25
38
|
},
|
|
26
39
|
findings: [],
|
|
27
40
|
operationBoundary: boundary(),
|
|
@@ -4,7 +4,8 @@ import { normalizePortableInput } from '../input/portable.input.js';
|
|
|
4
4
|
import { portableFinding, summarizePortableFindings } from '../findings.js';
|
|
5
5
|
import { planPortableArtifact } from '../schema/schema.guide.js';
|
|
6
6
|
import { indexPortableLoadedParentRecords, projectPortableLoadedParentRecord, resolvePortableLoadedParentReference } from './loaded.parent.js';
|
|
7
|
-
import { allocateContinuationPath, allocateRootArtifactPath } from '../../../transitions/record.transitions.js';
|
|
7
|
+
import { allocateContinuationPath, allocateDirectoryArtifactPath, allocateRootArtifactPath } from '../../../transitions/record.transitions.js';
|
|
8
|
+
import { normalizePortableParentRecord, qualifyPortableExactParent } from '../draft/draft.exact.js';
|
|
8
9
|
|
|
9
10
|
export const PORTABLE_EPISTEMIC_PLAN_SCHEMA_ID = 'tiinex.portable.epistemic-materialization-plan.v1';
|
|
10
11
|
|
|
@@ -69,18 +70,20 @@ function planProposal({ proposal, index, proposals, proposalIds, loadedIndex, ma
|
|
|
69
70
|
const clarificationNeeds = [];
|
|
70
71
|
const contract = buildArtifactCreationContract({ schemaId: proposal.schemaId, transitionType: proposal.parentRef ? 'continue-from-record' : 'create-artifact' });
|
|
71
72
|
const artifactPlan = planPortableArtifact({ ...material, schemaId: proposal.schemaId, task: proposal.parentRef ? 'continue' : 'create', values: proposal.values || {}, inputs: proposal.values || {} }, options);
|
|
72
|
-
const parent = resolveParentReference(proposal.parentRef, { loadedIndex, proposalIds, proposalIndex: index, proposals });
|
|
73
|
+
const parent = resolveParentReference(proposal.parentRef, { loadedIndex, proposalIds, proposalIndex: index, proposals, suppliedParentRecord: proposal.parentRecord });
|
|
73
74
|
const parentForAllocation = parent.status === 'resolved'
|
|
74
75
|
? parent.kind === 'proposal'
|
|
75
76
|
? plannedParentRecord(plannedById.get(parent.parent?.proposalId || ''))
|
|
76
77
|
: parent.parent
|
|
77
78
|
: null;
|
|
78
|
-
const allocationOptions = Object.freeze({ existingPaths: Object.freeze([...occupiedPaths]), path: proposal.path || '' });
|
|
79
|
+
const allocationOptions = Object.freeze({ existingPaths: Object.freeze([...occupiedPaths]), path: proposal.path || '', targetDirectory: proposal.targetDirectory || '' });
|
|
79
80
|
const allocated = proposal.parentRef && parentForAllocation
|
|
80
81
|
? allocateContinuationPath({ parentRecord: parentForAllocation, targetId: proposal.schemaId, targetLabel: proposal.schemaId, title: proposal.title || proposal.summary || proposal.id }, allocationOptions)
|
|
81
|
-
: !proposal.parentRef
|
|
82
|
-
?
|
|
83
|
-
:
|
|
82
|
+
: !proposal.parentRef && proposal.targetDirectory
|
|
83
|
+
? allocateDirectoryArtifactPath({ targetDirectory: proposal.targetDirectory, targetId: proposal.schemaId, targetLabel: proposal.schemaId, title: proposal.title || proposal.summary || proposal.id }, allocationOptions)
|
|
84
|
+
: !proposal.parentRef
|
|
85
|
+
? allocateRootArtifactPath({ targetId: proposal.schemaId, targetLabel: proposal.schemaId, title: proposal.title || proposal.summary || proposal.id }, allocationOptions)
|
|
86
|
+
: Object.freeze({ path: proposal.path || '', policy: Object.freeze({}) });
|
|
84
87
|
|
|
85
88
|
if (!proposal.schemaId) {
|
|
86
89
|
findings.push(portableFinding('error', 'portable.materialization.schema.required', 'Each proposal must declare an implemented schema id; schema meaning is never inferred by the writer.', { proposalId: proposal.id }));
|
|
@@ -130,6 +133,7 @@ function planProposal({ proposal, index, proposals, proposalIds, loadedIndex, ma
|
|
|
130
133
|
rationale: proposal.rationale,
|
|
131
134
|
evidenceRefs: proposal.evidenceRefs,
|
|
132
135
|
parentRef: proposal.parentRef,
|
|
136
|
+
targetDirectory: proposal.targetDirectory,
|
|
133
137
|
parent: parent.status === 'resolved' ? parent.parent : null,
|
|
134
138
|
parentKind: parent.status === 'resolved' ? parent.kind : '',
|
|
135
139
|
creationContract: contract,
|
|
@@ -148,6 +152,8 @@ function normalizeProposals(value) {
|
|
|
148
152
|
mode: clean(raw.mode || ''),
|
|
149
153
|
schemaId: clean(raw.schemaId || raw.schema || ''),
|
|
150
154
|
parentRef: exact(raw.parentRef ?? raw.parent ?? ''),
|
|
155
|
+
parentRecord: raw.parentRecord && typeof raw.parentRecord === 'object' ? Object.freeze(clone(raw.parentRecord)) : null,
|
|
156
|
+
targetDirectory: clean(raw.targetDirectory || raw.directory || ''),
|
|
151
157
|
path: clean(raw.path || ''),
|
|
152
158
|
title: clean(raw.title || ''),
|
|
153
159
|
summary: clean(raw.summary || ''),
|
|
@@ -168,9 +174,17 @@ function plannedParentRecord(entry = null) {
|
|
|
168
174
|
return Object.freeze({ id: entry.id || '', path: entry.path, schemaId: entry.schemaId || '', title: entry.title || entry.summary || entry.id || '' });
|
|
169
175
|
}
|
|
170
176
|
|
|
171
|
-
function resolveParentReference(ref, { loadedIndex, proposalIds, proposalIndex, proposals }) {
|
|
177
|
+
function resolveParentReference(ref, { loadedIndex, proposalIds, proposalIndex, proposals, suppliedParentRecord = null }) {
|
|
172
178
|
if (!ref) return { status: 'none', kind: 'none', parent: null };
|
|
173
179
|
const reference = exact(ref);
|
|
180
|
+
if (suppliedParentRecord && typeof suppliedParentRecord === 'object') {
|
|
181
|
+
const normalized = normalizePortableParentRecord(suppliedParentRecord);
|
|
182
|
+
const declared = new Set([exact(normalized.id), exact(normalized.path)].filter(Boolean));
|
|
183
|
+
if (!declared.has(reference)) return { status: 'mismatch', code: 'portable.materialization.parent.supplied-reference-mismatch', message: 'The supplied Parent record does not identify the declared Parent reference.', candidates: [...declared] };
|
|
184
|
+
const qualification = qualifyPortableExactParent(normalized, 'continue-from-record');
|
|
185
|
+
if (!['qualified', 'qualified-local-continuity'].includes(String(qualification.state || ''))) return { status: 'unqualified', code: `portable.materialization.parent.supplied-${qualification.reason || 'unqualified'}`, message: 'The supplied Parent record is not qualified for continuation authoring.', candidates: [] };
|
|
186
|
+
return { status: 'resolved', kind: 'supplied-record', parent: qualification.snapshot };
|
|
187
|
+
}
|
|
174
188
|
const proposalRef = reference.startsWith('proposal:') ? reference.slice('proposal:'.length) : proposalIds.has(reference) ? reference : '';
|
|
175
189
|
if (proposalRef) {
|
|
176
190
|
const targetIndex = proposals.findIndex((proposal) => proposal.id === proposalRef);
|
|
@@ -46,7 +46,7 @@ export function preparePortableWorkspaceInitialization(input = {}) {
|
|
|
46
46
|
if (!integrityTarget) findings.push(finding('error', 'workspace.initialize.integrity-method.unresolved', 'Core could not resolve canonical c14n-v2 validator authority.'));
|
|
47
47
|
if (findings.some((item) => item.severity === 'error')) return blocked(findings);
|
|
48
48
|
|
|
49
|
-
const entrypoint = repository ? `\n## Workspace Entrypoints\n\n### Repository source\n\n- Source Kind: ${sourceKind}\n- Repository: ${repository}${ref ? `\n- Ref: ${ref}` : ''}\n- Root Path: ${rootPath}\n- Repo Files Discovery: on\n` : '';
|
|
49
|
+
const entrypoint = repository ? `\n\n## Workspace Entrypoints\n\n### Repository source\n\n- Source Kind: ${sourceKind}\n- Repository: ${repository}${ref ? `\n- Ref: ${ref}` : ''}\n- Root Path: ${rootPath}\n- Repo Files Discovery: on\n` : '';
|
|
50
50
|
const unsigned = `# Continuity Context\n\n- Envelope Schema: [tiinex.root.v1](${envelopeTarget})\n- Current\n - Current Schema: [tiinex.workspace.v1](${schemaTarget})\n - Created At: ${createdAt}\n - Authors: ${authors}\n - Why: Establish an explicit portable Workspace entrypoint for this repository.\n - Summary: ${title} Workspace.\n - Status: active/local\n\n---\n\n# ${title}${entrypoint}\n# Continuity Integrity\n\n- [sha256-base64url-c14n-v2](${integrityTarget})\n - Towards: self\n - Value: pending\n`;
|
|
51
51
|
const sealed = sealC14nV2Self(unsigned);
|
|
52
52
|
if (sealed.state !== 'sealed') return blocked([finding('error', 'workspace.initialize.integrity-seal.failed', 'Core could not seal Workspace c14n-v2 self integrity.', { reason: sealed.reason || sealed.state })]);
|
|
@@ -169,19 +169,21 @@ export function allocateContinuationPath({ parentRecord = {}, targetId = '', tar
|
|
|
169
169
|
const explicitPath = canonicalLocalPath(options.path || options.draftPath || '');
|
|
170
170
|
if (explicitPath) return { path: uniqueTransitionPath(explicitPath, occupied), policy: pathPolicyForExplicit(explicitPath) };
|
|
171
171
|
const parentPath = externalWebArtifactUrl(parentRecord) ? '' : canonicalLocalPath(parentRecord.path || parentRecord.sourcePath || parentRecord.sourceTarget?.sourceArtifactPath || '');
|
|
172
|
-
const parentDir = parentDirectory(parentPath)
|
|
173
|
-
const
|
|
174
|
-
const
|
|
172
|
+
const parentDir = parentDirectory(parentPath);
|
|
173
|
+
const requestedRaw = String(options.targetDirectory || options.directory || '').trim();
|
|
174
|
+
const requestedExplicit = Boolean(requestedRaw);
|
|
175
|
+
const requestedDir = requestedRaw === '.' ? '' : canonicalLocalPath(requestedRaw);
|
|
176
|
+
const targetDir = requestedExplicit ? requestedDir : (parentDir || '.topics');
|
|
175
177
|
const parentPrefix = lineagePrefixFromPath(parentPath);
|
|
176
178
|
const labelSlug = slugify(title || parentRecord.title || targetLabel || 'continuation');
|
|
177
179
|
const targetSlug = slugify(targetLabel || labelFromSchemaId(targetId) || 'leaf');
|
|
178
180
|
const extension = '.trace.md';
|
|
179
|
-
const directoryLocal = Boolean(
|
|
181
|
+
const directoryLocal = Boolean(requestedExplicit && requestedDir !== parentDir);
|
|
180
182
|
const policy = {
|
|
181
183
|
schema: 'tiinex.transition.path-policy.v1',
|
|
182
184
|
kind: directoryLocal ? 'directory-local-continuation' : 'same-parent-directory',
|
|
183
185
|
parentDirectory: parentDir,
|
|
184
|
-
targetDirectory: targetDir,
|
|
186
|
+
targetDirectory: requestedExplicit && requestedDir === '' ? '.' : targetDir,
|
|
185
187
|
parentPath,
|
|
186
188
|
parentLineagePrefix: parentPrefix,
|
|
187
189
|
labelSlug,
|
|
@@ -196,14 +198,16 @@ export function allocateDirectoryArtifactPath({ targetDirectory = '.topics', tar
|
|
|
196
198
|
const occupied = existingTransitionPaths(options);
|
|
197
199
|
const explicitPath = canonicalLocalPath(options.path || options.draftPath || '');
|
|
198
200
|
if (explicitPath) return { path: uniqueTransitionPath(explicitPath, occupied), policy: pathPolicyForExplicit(explicitPath) };
|
|
199
|
-
const
|
|
201
|
+
const targetRaw = String(targetDirectory ?? '').trim();
|
|
202
|
+
const explicitRoot = targetRaw === '.';
|
|
203
|
+
const dir = explicitRoot ? '' : (canonicalLocalPath(targetRaw || '.topics') || '.topics');
|
|
200
204
|
const labelSlug = slugify(title || targetLabel || labelFromSchemaId(targetId) || 'artifact');
|
|
201
205
|
const targetSlug = slugify(targetLabel || labelFromSchemaId(targetId) || 'artifact');
|
|
202
206
|
const extension = '.trace.md';
|
|
203
207
|
const policy = {
|
|
204
208
|
schema: 'tiinex.transition.path-policy.v1',
|
|
205
209
|
kind: 'directory-local-root',
|
|
206
|
-
targetDirectory: dir,
|
|
210
|
+
targetDirectory: explicitRoot ? '.' : dir,
|
|
207
211
|
labelSlug,
|
|
208
212
|
targetSlug,
|
|
209
213
|
extension,
|
|
@@ -214,20 +218,25 @@ export function allocateDirectoryArtifactPath({ targetDirectory = '.topics', tar
|
|
|
214
218
|
|
|
215
219
|
function pathFromPolicy(policy = {}, occupied = new Set()) {
|
|
216
220
|
const kind = String(policy.kind || '').trim();
|
|
217
|
-
const
|
|
221
|
+
const hasTargetDirectory = Object.prototype.hasOwnProperty.call(policy, 'targetDirectory');
|
|
222
|
+
const hasParentDirectory = Object.prototype.hasOwnProperty.call(policy, 'parentDirectory');
|
|
223
|
+
const rawDirectory = hasTargetDirectory ? String(policy.targetDirectory ?? '') : hasParentDirectory ? String(policy.parentDirectory ?? '') : '.topics';
|
|
224
|
+
const rootDirectory = rawDirectory === '.' || ((hasTargetDirectory || hasParentDirectory) && rawDirectory === '');
|
|
225
|
+
const dir = rootDirectory ? '' : (canonicalLocalPath(rawDirectory || '.topics') || '.topics');
|
|
226
|
+
const pathPrefix = dir ? `${dir}/` : '';
|
|
218
227
|
const extension = String(policy.extension || '.trace.md').startsWith('.') ? String(policy.extension || '.trace.md') : `.${policy.extension}`;
|
|
219
228
|
const labelSlug = slugify(policy.labelSlug || 'continuation');
|
|
220
229
|
const targetSlug = slugify(policy.targetSlug || 'leaf');
|
|
221
230
|
const parentPrefix = String(policy.parentLineagePrefix || '').trim();
|
|
222
231
|
if (kind === 'directory-local-root' || kind === 'directory-local-continuation') {
|
|
223
232
|
const rootPrefix = nextDirectoryRootLineagePrefix(dir, occupied);
|
|
224
|
-
return `${
|
|
233
|
+
return `${pathPrefix}${rootPrefix}-${labelSlug}.${extension.replace(/^\./, '')}`;
|
|
225
234
|
}
|
|
226
235
|
if (parentPrefix) {
|
|
227
236
|
const childPrefix = nextChildLineagePrefix(parentPrefix, dir, occupied);
|
|
228
|
-
return `${
|
|
237
|
+
return `${pathPrefix}${childPrefix}-${labelSlug}.${extension.replace(/^\./, '')}`;
|
|
229
238
|
}
|
|
230
|
-
return uniqueTransitionPath(`${
|
|
239
|
+
return uniqueTransitionPath(`${pathPrefix}${labelSlug}--${targetSlug}${extension}`, occupied);
|
|
231
240
|
}
|
|
232
241
|
|
|
233
242
|
function pathPolicyForExplicit(path = '') {
|