@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiinex/core",
3
- "version": "0.27.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": "a7e75e17b1b95c022875c2006d2957d8cce580ae",
176
+ "gitHead": "d2ab9eeaaf1a0c0d13e2d7a5f9de4d010cf5e5bb",
177
177
  "tiinexRelease": {
178
178
  "policy": "tiinex.master-npm-release.v1",
179
- "sourceCommit": "a7e75e17b1b95c022875c2006d2957d8cce580ae",
180
- "sourceTree": "e86d119618fcd9cdc48bff5b016546f79bb7bcaa",
179
+ "sourceCommit": "d2ab9eeaaf1a0c0d13e2d7a5f9de4d010cf5e5bb",
180
+ "sourceTree": "50ae220f210ec6551825735fb7d9ba8f7956edc5",
181
181
  "repository": "Tiinex/core",
182
- "previousVersion": "0.26.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 relativeReference = relativePath(dirname(options.childPath || draft.path || ''), parentRecord.path || '');
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 recoveryMode = String(parentRecord?.recoveryMode || parentRecord?.parentRecoveryMode || '').trim() === 'external-versioned' ? 'external-versioned' : 'local-relative';
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 recoveryMode = String(parentRecord?.recoveryMode || parentRecord?.parentRecoveryMode || '').trim() === 'external-versioned' ? 'external-versioned' : 'local-relative';
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 recoveryMode = String(parent.recoveryMode || '').trim() === 'external-versioned' ? 'external-versioned' : 'local-relative';
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: String(record.path || record.id || ''), path: String(record.path || record.id || ''), schemaId, currentSchemaId: schemaId, currentCreatedAt: createdAt,
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: { schemaId, preferredTarget: schemaTarget, exactTargets: schemaTarget ? [schemaTarget] : [], resolutionState: 'unresolved', evidence: { basis: 'declared-current-schema-reference-only' } }
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
- ? allocateRootArtifactPath({ targetId: proposal.schemaId, targetLabel: proposal.schemaId, title: proposal.title || proposal.summary || proposal.id }, allocationOptions)
83
- : Object.freeze({ path: proposal.path || '', policy: Object.freeze({}) });
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) || '.topics';
173
- const requestedDir = canonicalLocalPath(options.targetDirectory || options.directory || '');
174
- const targetDir = requestedDir || parentDir;
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(requestedDir && requestedDir !== parentDir);
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 dir = canonicalLocalPath(targetDirectory || '.topics') || '.topics';
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 dir = canonicalLocalPath(policy.targetDirectory || policy.parentDirectory || '.topics') || '.topics';
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 `${dir}/${rootPrefix}-${labelSlug}.${extension.replace(/^\./, '')}`;
233
+ return `${pathPrefix}${rootPrefix}-${labelSlug}.${extension.replace(/^\./, '')}`;
225
234
  }
226
235
  if (parentPrefix) {
227
236
  const childPrefix = nextChildLineagePrefix(parentPrefix, dir, occupied);
228
- return `${dir}/${childPrefix}-${labelSlug}.${extension.replace(/^\./, '')}`;
237
+ return `${pathPrefix}${childPrefix}-${labelSlug}.${extension.replace(/^\./, '')}`;
229
238
  }
230
- return uniqueTransitionPath(`${dir}/${labelSlug}--${targetSlug}${extension}`, occupied);
239
+ return uniqueTransitionPath(`${pathPrefix}${labelSlug}--${targetSlug}${extension}`, occupied);
231
240
  }
232
241
 
233
242
  function pathPolicyForExplicit(path = '') {