@pennixrv/trellis 0.7.0-beta.5 → 0.7.0-beta.50
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/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +47 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/backup.d.ts +25 -0
- package/dist/commands/backup.d.ts.map +1 -0
- package/dist/commands/backup.js +152 -0
- package/dist/commands/backup.js.map +1 -0
- package/dist/commands/channel/adapters/claude.d.ts.map +1 -1
- package/dist/commands/channel/adapters/claude.js +2 -0
- package/dist/commands/channel/adapters/claude.js.map +1 -1
- package/dist/commands/channel/adapters/codex.d.ts +5 -1
- package/dist/commands/channel/adapters/codex.d.ts.map +1 -1
- package/dist/commands/channel/adapters/codex.js +19 -9
- package/dist/commands/channel/adapters/codex.js.map +1 -1
- package/dist/commands/channel/adapters/index.d.ts +3 -0
- package/dist/commands/channel/adapters/index.d.ts.map +1 -1
- package/dist/commands/channel/adapters/index.js +3 -3
- package/dist/commands/channel/adapters/index.js.map +1 -1
- package/dist/commands/channel/adapters/types.d.ts +2 -0
- package/dist/commands/channel/adapters/types.d.ts.map +1 -1
- package/dist/commands/channel/guard.d.ts.map +1 -1
- package/dist/commands/channel/guard.js +38 -29
- package/dist/commands/channel/guard.js.map +1 -1
- package/dist/commands/channel/index.d.ts.map +1 -1
- package/dist/commands/channel/index.js +52 -16
- package/dist/commands/channel/index.js.map +1 -1
- package/dist/commands/channel/kill.d.ts.map +1 -1
- package/dist/commands/channel/kill.js +37 -15
- package/dist/commands/channel/kill.js.map +1 -1
- package/dist/commands/channel/messages.d.ts.map +1 -1
- package/dist/commands/channel/messages.js +3 -2
- package/dist/commands/channel/messages.js.map +1 -1
- package/dist/commands/channel/process-identity.d.ts +3 -0
- package/dist/commands/channel/process-identity.d.ts.map +1 -0
- package/dist/commands/channel/process-identity.js +24 -0
- package/dist/commands/channel/process-identity.js.map +1 -0
- package/dist/commands/channel/profiles.d.ts +26 -0
- package/dist/commands/channel/profiles.d.ts.map +1 -0
- package/dist/commands/channel/profiles.js +186 -0
- package/dist/commands/channel/profiles.js.map +1 -0
- package/dist/commands/channel/rm.d.ts +0 -1
- package/dist/commands/channel/rm.d.ts.map +1 -1
- package/dist/commands/channel/rm.js +203 -96
- package/dist/commands/channel/rm.js.map +1 -1
- package/dist/commands/channel/spawn.d.ts +3 -0
- package/dist/commands/channel/spawn.d.ts.map +1 -1
- package/dist/commands/channel/spawn.js +43 -15
- package/dist/commands/channel/spawn.js.map +1 -1
- package/dist/commands/channel/supervisor/idle.d.ts +2 -0
- package/dist/commands/channel/supervisor/idle.d.ts.map +1 -1
- package/dist/commands/channel/supervisor/idle.js +15 -7
- package/dist/commands/channel/supervisor/idle.js.map +1 -1
- package/dist/commands/channel/supervisor/inbox.d.ts.map +1 -1
- package/dist/commands/channel/supervisor/inbox.js +48 -33
- package/dist/commands/channel/supervisor/inbox.js.map +1 -1
- package/dist/commands/channel/supervisor/stdout.d.ts.map +1 -1
- package/dist/commands/channel/supervisor/stdout.js +6 -3
- package/dist/commands/channel/supervisor/stdout.js.map +1 -1
- package/dist/commands/channel/supervisor/warning.d.ts.map +1 -1
- package/dist/commands/channel/supervisor/warning.js +2 -3
- package/dist/commands/channel/supervisor/warning.js.map +1 -1
- package/dist/commands/channel/supervisor.d.ts +12 -0
- package/dist/commands/channel/supervisor.d.ts.map +1 -1
- package/dist/commands/channel/supervisor.js +88 -16
- package/dist/commands/channel/supervisor.js.map +1 -1
- package/dist/commands/channel/wait.d.ts +3 -1
- package/dist/commands/channel/wait.d.ts.map +1 -1
- package/dist/commands/channel/wait.js +59 -14
- package/dist/commands/channel/wait.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +23 -10
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +446 -333
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/workflow.d.ts +1 -0
- package/dist/commands/workflow.d.ts.map +1 -1
- package/dist/commands/workflow.js +56 -1
- package/dist/commands/workflow.js.map +1 -1
- package/dist/configurators/codex.d.ts +6 -0
- package/dist/configurators/codex.d.ts.map +1 -1
- package/dist/configurators/codex.js +93 -0
- package/dist/configurators/codex.js.map +1 -1
- package/dist/configurators/shared.js +2 -2
- package/dist/configurators/shared.js.map +1 -1
- package/dist/constants/paths.d.ts +2 -0
- package/dist/constants/paths.d.ts.map +1 -1
- package/dist/constants/paths.js +2 -0
- package/dist/constants/paths.js.map +1 -1
- package/dist/migrations/manifests/0.7.0-beta.10.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.11.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.12.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.13.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.14.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.15.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.16.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.17.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.18.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.19.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.20.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.21.json +17 -0
- package/dist/migrations/manifests/0.7.0-beta.22.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.23.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.24.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.25.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.26.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.27.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.28.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.29.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.30.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.31.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.32.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.33.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.34.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.35.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.36.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.37.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.38.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.39.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.40.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.41.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.42.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.43.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.44.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.45.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.46.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.47.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.48.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.49.json +13 -0
- package/dist/migrations/manifests/0.7.0-beta.50.json +12 -0
- package/dist/migrations/manifests/0.7.0-beta.6.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.7.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.8.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.9.json +9 -0
- package/dist/templates/codex/hooks/session-start.py +21 -17
- package/dist/templates/common/bundled-skills/trellis-channel/SKILL.md +3 -1
- package/dist/templates/common/bundled-skills/trellis-channel/references/command-reference.md +24 -16
- package/dist/templates/common/bundled-skills/trellis-channel/references/forum.md +4 -12
- package/dist/templates/common/bundled-skills/trellis-channel/references/multi-target-dispatch.md +139 -0
- package/dist/templates/common/bundled-skills/trellis-channel/references/progress-debugging.md +9 -5
- package/dist/templates/common/bundled-skills/trellis-channel/references/subnode-work.md +84 -14
- package/dist/templates/common/bundled-skills/trellis-channel/references/workers.md +9 -11
- package/dist/templates/common/bundled-skills/trellis-channel/references/workflows.md +25 -14
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-skills-or-commands.md +13 -11
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-task-lifecycle.md +7 -2
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/task-system.md +6 -1
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/workflow.md +6 -4
- package/dist/templates/common/bundled-skills/trellis-session-insight/SKILL.md +42 -3
- package/dist/templates/common/bundled-skills/trellis-session-insight/references/cli-quick-reference.md +1 -1
- package/dist/templates/common/bundled-skills/trellis-session-insight/references/triggering-patterns.md +10 -2
- package/dist/templates/common/commands/continue.md +36 -8
- package/dist/templates/common/commands/finish-work.md +17 -2
- package/dist/templates/common/commands/start.md +8 -1
- package/dist/templates/common/skills/brainstorm.md +49 -22
- package/dist/templates/common/skills/break-loop.md +6 -10
- package/dist/templates/common/skills/check.md +34 -5
- package/dist/templates/copilot/hooks/session-start.py +19 -15
- package/dist/templates/copilot/prompts/brainstorm.prompt.md +27 -21
- package/dist/templates/markdown/spec/backend/database-guidelines.md.txt +22 -22
- package/dist/templates/markdown/spec/backend/directory-structure.md.txt +20 -20
- package/dist/templates/markdown/spec/backend/error-handling.md.txt +22 -22
- package/dist/templates/markdown/spec/backend/index.md.txt +19 -19
- package/dist/templates/markdown/spec/backend/logging-guidelines.md.txt +22 -22
- package/dist/templates/markdown/spec/backend/quality-guidelines.md.txt +22 -22
- package/dist/templates/markdown/spec/frontend/component-guidelines.md.txt +25 -25
- package/dist/templates/markdown/spec/frontend/directory-structure.md.txt +20 -20
- package/dist/templates/markdown/spec/frontend/hook-guidelines.md.txt +22 -22
- package/dist/templates/markdown/spec/frontend/index.md.txt +20 -20
- package/dist/templates/markdown/spec/frontend/quality-guidelines.md.txt +22 -22
- package/dist/templates/markdown/spec/frontend/state-management.md.txt +22 -22
- package/dist/templates/markdown/spec/frontend/type-safety.md.txt +22 -22
- package/dist/templates/markdown/spec/guides/code-reuse-thinking-guide.md.txt +80 -85
- package/dist/templates/markdown/spec/guides/cross-layer-thinking-guide.md.txt +164 -209
- package/dist/templates/markdown/spec/guides/cross-platform-thinking-guide.md.txt +161 -185
- package/dist/templates/markdown/spec/guides/index.md.txt +51 -51
- package/dist/templates/markdown/workspace-index.md +44 -44
- package/dist/templates/opencode/lib/session-utils.js +2 -2
- package/dist/templates/shared-hooks/inject-shell-session-context.py +3 -6
- package/dist/templates/shared-hooks/inject-workflow-state.py +3 -2
- package/dist/templates/shared-hooks/session-start.py +23 -18
- package/dist/templates/trellis/agents/subnode-profiles.json +13 -0
- package/dist/templates/trellis/agents/subnode.md +11 -5
- package/dist/templates/trellis/config.yaml +2 -0
- package/dist/templates/trellis/index.d.ts +2 -1
- package/dist/templates/trellis/index.d.ts.map +1 -1
- package/dist/templates/trellis/index.js +4 -2
- package/dist/templates/trellis/index.js.map +1 -1
- package/dist/templates/trellis/scripts/add_session.py +35 -32
- package/dist/templates/trellis/scripts/common/active_task.py +8 -8
- package/dist/templates/trellis/scripts/common/developer.py +18 -18
- package/dist/templates/trellis/scripts/common/ownership_record.py +13 -8
- package/dist/templates/trellis/scripts/common/safe_commit.py +38 -9
- package/dist/templates/trellis/scripts/common/task_planning.py +137 -0
- package/dist/templates/trellis/scripts/common/task_store.py +46 -14
- package/dist/templates/trellis/scripts/common/workflow_phase.py +4 -3
- package/dist/templates/trellis/scripts/subnode_artifact.py +470 -21
- package/dist/templates/trellis/scripts/task.py +175 -2
- package/dist/templates/trellis/workflow.md +407 -388
- package/dist/utils/managed-paths.d.ts +2 -0
- package/dist/utils/managed-paths.d.ts.map +1 -1
- package/dist/utils/managed-paths.js +24 -2
- package/dist/utils/managed-paths.js.map +1 -1
- package/dist/utils/template-fetcher.d.ts +1 -0
- package/dist/utils/template-fetcher.d.ts.map +1 -1
- package/dist/utils/template-fetcher.js +2 -1
- package/dist/utils/template-fetcher.js.map +1 -1
- package/dist/utils/uninstall-scrubbers.d.ts.map +1 -1
- package/dist/utils/uninstall-scrubbers.js +5 -0
- package/dist/utils/uninstall-scrubbers.js.map +1 -1
- package/dist/utils/workflow-provenance.d.ts +24 -0
- package/dist/utils/workflow-provenance.d.ts.map +1 -0
- package/dist/utils/workflow-provenance.js +71 -0
- package/dist/utils/workflow-provenance.js.map +1 -0
- package/dist/utils/workflow-resolver.d.ts +9 -2
- package/dist/utils/workflow-resolver.d.ts.map +1 -1
- package/dist/utils/workflow-resolver.js +33 -7
- package/dist/utils/workflow-resolver.js.map +1 -1
- package/package.json +2 -2
- package/dist/templates/trellis/agents/subnode.env +0 -4
package/dist/commands/update.js
CHANGED
|
@@ -4,6 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import inquirer from "inquirer";
|
|
6
6
|
import { DIR_NAMES, FILE_NAMES, PATHS } from "../constants/paths.js";
|
|
7
|
+
import { BACKUP_COMPLETE_MARKER, BACKUP_PRUNE_LOCK } from "./backup.js";
|
|
7
8
|
import { VERSION, PACKAGE_NAME } from "../constants/version.js";
|
|
8
9
|
import { getMigrationsForVersion, getAllMigrations, getMigrationMetadata, getConfigSectionsAddedBetween, } from "../migrations/index.js";
|
|
9
10
|
import { loadHashes, saveHashes, updateHashes, isTemplateModified, removeHash, renameHash, computeHash, shouldExcludeFromHash, } from "../utils/template-hash.js";
|
|
@@ -20,14 +21,16 @@ import { COPILOT_INSTRUCTIONS_BLOCK_END, COPILOT_INSTRUCTIONS_BLOCK_START, COPIL
|
|
|
20
21
|
import { ALL_MANAGED_DIRS, getConfiguredPlatforms, collectPlatformTemplates, } from "../configurators/index.js";
|
|
21
22
|
import { replacePythonCommandLiterals } from "../configurators/shared.js";
|
|
22
23
|
import { CLAUDE_STATUSLINE_PATH, isClaudeStatuslineManaged, } from "../configurators/claude.js";
|
|
23
|
-
import { preserveCodexAgentModelKeys } from "../configurators/codex.js";
|
|
24
|
+
import { preserveCodexAgentModelKeys, preserveCodexProjectConfig, } from "../configurators/codex.js";
|
|
24
25
|
import { printZcodeSetupHint } from "../configurators/zcode.js";
|
|
25
26
|
import { ensureGitattributes } from "../configurators/workflow.js";
|
|
26
27
|
import { getStatuslineHook } from "../templates/claude/index.js";
|
|
27
28
|
import { pruneOrphanManifestKeys } from "../utils/manifest-prune.js";
|
|
28
29
|
import { fetchRegistrySpecTemplates, collectDirectoryFiles, removeDirectory, parseRegistrySource, probeRegistryIndex, downloadTemplateById, } from "../utils/template-fetcher.js";
|
|
29
30
|
import { loadSpecRegistryConfig } from "../utils/registry-config.js";
|
|
30
|
-
import { cleanupEmptyDirs, getManagedMarkdownBlock, mergeManagedMarkdownBlock, TRELLIS_BLOCK_END, TRELLIS_BLOCK_START, } from "../utils/managed-paths.js";
|
|
31
|
+
import { cleanupEmptyDirs, getManagedMarkdownBlock, mergeManagedMarkdownBlock, reportAgentsOverride, TRELLIS_BLOCK_END, TRELLIS_BLOCK_START, } from "../utils/managed-paths.js";
|
|
32
|
+
import { loadWorkflowProvenance, writeWorkflowProvenance, workflowProvenanceMismatches, } from "../utils/workflow-provenance.js";
|
|
33
|
+
import { NATIVE_WORKFLOW_ID, resolveWorkflowTemplate, } from "../utils/workflow-resolver.js";
|
|
31
34
|
export { cleanupEmptyDirs, TRELLIS_BLOCK_END, TRELLIS_BLOCK_START, } from "../utils/managed-paths.js";
|
|
32
35
|
const CLAUDE_SETTINGS_PATH = ".claude/settings.json";
|
|
33
36
|
const LEGACY_UNTRACKED_AGENTS_MD_BLOCK_HASHES = new Set([
|
|
@@ -37,73 +40,6 @@ const LEGACY_UNTRACKED_AGENTS_MD_BLOCK_HASHES = new Set([
|
|
|
37
40
|
// false "modified by you" conflict.
|
|
38
41
|
"c1f511b1cfc1902f2147da159f09cc51f380b0c9e341cdb3ac5dea5233f3e307",
|
|
39
42
|
]);
|
|
40
|
-
const CODEX_WORKFLOW_STATE_HOOK_PATH = ".codex/hooks/inject-workflow-state.py";
|
|
41
|
-
const LEGACY_PENNIX_CODEX_DISPATCH_SIGNATURES = [
|
|
42
|
-
"def _resolve_codex_dispatch_mode(config: dict, repo_root: Path | None = None) -> str:",
|
|
43
|
-
"from common.config import get_workflow_dispatch_mode",
|
|
44
|
-
"return get_workflow_dispatch_mode(root, config=config)",
|
|
45
|
-
];
|
|
46
|
-
const LEGACY_PENNIX_ACTIVE_TASK_GUARD = " active = _resolve_active_task(root, input_data)\n" +
|
|
47
|
-
" if not active.task_path:\n" +
|
|
48
|
-
" return None";
|
|
49
|
-
const PENNIX_AMBIGUITY_PROJECTION = " active = _resolve_active_task(root, input_data)\n" +
|
|
50
|
-
' if active.source_type == "unbound_ambiguous":\n' +
|
|
51
|
-
' candidates = ", ".join(active.candidate_paths)\n' +
|
|
52
|
-
' return candidates, "unbound_ambiguous", active.source\n' +
|
|
53
|
-
" if not active.task_path:\n" +
|
|
54
|
-
" return None";
|
|
55
|
-
const LEGACY_PENNIX_BREADCRUMB_HEADER = ' header = f"Status: {status}" if task_id is None else f"Task: {task_id} ({status})"';
|
|
56
|
-
const PENNIX_AMBIGUITY_BREADCRUMB_HEADER = ' if status == "unbound_ambiguous":\n' +
|
|
57
|
-
' header = f"Status: {status}\\nCandidates: {task_id}"\n' +
|
|
58
|
-
" else:\n" +
|
|
59
|
-
' header = f"Status: {status}" if task_id is None else f"Task: {task_id} ({status})"';
|
|
60
|
-
/**
|
|
61
|
-
* Preserve the historical Pennix Codex hook while adding the one projection
|
|
62
|
-
* it predates. This is intentionally stricter than hash tracking: the Hook
|
|
63
|
-
* carried a Pennix-specific dispatch resolver, so pristine installations were
|
|
64
|
-
* correctly classified as locally modified and never received issue #180.
|
|
65
|
-
*/
|
|
66
|
-
function migrateLegacyPennixCodexWorkflowHook(relativePath, existingContent) {
|
|
67
|
-
if (relativePath !== CODEX_WORKFLOW_STATE_HOOK_PATH ||
|
|
68
|
-
!LEGACY_PENNIX_CODEX_DISPATCH_SIGNATURES.every((signature) => existingContent.includes(signature))) {
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
71
|
-
const hasProjection = existingContent.includes(PENNIX_AMBIGUITY_PROJECTION);
|
|
72
|
-
const hasHeader = existingContent.includes(PENNIX_AMBIGUITY_BREADCRUMB_HEADER);
|
|
73
|
-
if (hasProjection && hasHeader) {
|
|
74
|
-
return { content: existingContent, needsUpdate: false };
|
|
75
|
-
}
|
|
76
|
-
if ((!hasProjection &&
|
|
77
|
-
!existingContent.includes(LEGACY_PENNIX_ACTIVE_TASK_GUARD)) ||
|
|
78
|
-
(!hasHeader && !existingContent.includes(LEGACY_PENNIX_BREADCRUMB_HEADER))) {
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
return {
|
|
82
|
-
content: existingContent
|
|
83
|
-
.replace(LEGACY_PENNIX_ACTIVE_TASK_GUARD, PENNIX_AMBIGUITY_PROJECTION)
|
|
84
|
-
.replace(LEGACY_PENNIX_BREADCRUMB_HEADER, PENNIX_AMBIGUITY_BREADCRUMB_HEADER),
|
|
85
|
-
needsUpdate: !hasProjection || !hasHeader,
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
/** Keep recognized Pennix Hook customizations in the desired template map. */
|
|
89
|
-
function preserveLegacyPennixCodexWorkflowHook(cwd, templates) {
|
|
90
|
-
if (!templates.has(CODEX_WORKFLOW_STATE_HOOK_PATH)) {
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
const hookPath = path.join(cwd, CODEX_WORKFLOW_STATE_HOOK_PATH);
|
|
94
|
-
if (!fs.existsSync(hookPath)) {
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
try {
|
|
98
|
-
const migration = migrateLegacyPennixCodexWorkflowHook(CODEX_WORKFLOW_STATE_HOOK_PATH, fs.readFileSync(hookPath, "utf-8"));
|
|
99
|
-
if (migration) {
|
|
100
|
-
templates.set(CODEX_WORKFLOW_STATE_HOOK_PATH, migration.content);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
catch {
|
|
104
|
-
// The normal conflict path reports unreadable or concurrently changed files.
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
43
|
// Paths that should never be touched (true user data)
|
|
108
44
|
// spec/ is user-customized content created during init; update should never modify it
|
|
109
45
|
const PROTECTED_PATHS = [
|
|
@@ -127,6 +63,31 @@ function buildManagedBlockTemplate(cwd, relativePath, templateContent, startMark
|
|
|
127
63
|
function buildAgentsMdTemplate(cwd) {
|
|
128
64
|
return buildManagedBlockTemplate(cwd, FILE_NAMES.AGENTS, agentsMdContent, TRELLIS_BLOCK_START, TRELLIS_BLOCK_END);
|
|
129
65
|
}
|
|
66
|
+
function countOccurrences(content, needle) {
|
|
67
|
+
return content.split(needle).length - 1;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* A generated Markdown file can contain a user-owned document around one
|
|
71
|
+
* Trellis-managed block. If only that block changed, the whole file is still
|
|
72
|
+
* an update owned by Trellis and must not enter the user-conflict path.
|
|
73
|
+
*/
|
|
74
|
+
function isManagedMarkdownBlockOnlyChange(relativePath, existingContent, newContent) {
|
|
75
|
+
if (relativePath !== FILE_NAMES.AGENTS)
|
|
76
|
+
return false;
|
|
77
|
+
if (countOccurrences(existingContent, TRELLIS_BLOCK_START) !== 1 ||
|
|
78
|
+
countOccurrences(existingContent, TRELLIS_BLOCK_END) !== 1 ||
|
|
79
|
+
countOccurrences(newContent, TRELLIS_BLOCK_START) !== 1 ||
|
|
80
|
+
countOccurrences(newContent, TRELLIS_BLOCK_END) !== 1) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
const existingBlock = getTrellisManagedBlock(existingContent);
|
|
84
|
+
const newBlock = getTrellisManagedBlock(newContent);
|
|
85
|
+
return (existingBlock !== null &&
|
|
86
|
+
newBlock !== null &&
|
|
87
|
+
existingBlock !== newBlock &&
|
|
88
|
+
existingContent.replace(existingBlock, "") ===
|
|
89
|
+
newContent.replace(newBlock, ""));
|
|
90
|
+
}
|
|
130
91
|
function buildCopilotInstructionsTemplate(cwd) {
|
|
131
92
|
return buildManagedBlockTemplate(cwd, COPILOT_INSTRUCTIONS_PATH, getCopilotInstructions(), COPILOT_INSTRUCTIONS_BLOCK_START, COPILOT_INSTRUCTIONS_BLOCK_END);
|
|
132
93
|
}
|
|
@@ -134,6 +95,9 @@ function isKnownUntrackedTemplate(relativePath, existingContent) {
|
|
|
134
95
|
if (relativePath !== FILE_NAMES.AGENTS) {
|
|
135
96
|
return false;
|
|
136
97
|
}
|
|
98
|
+
if (countOccurrences(existingContent, TRELLIS_BLOCK_START) !== 1 ||
|
|
99
|
+
countOccurrences(existingContent, TRELLIS_BLOCK_END) !== 1)
|
|
100
|
+
return false;
|
|
137
101
|
const managedBlock = getTrellisManagedBlock(existingContent);
|
|
138
102
|
if (!managedBlock) {
|
|
139
103
|
return false;
|
|
@@ -513,6 +477,58 @@ function preserveExistingRegistryConfig(cwd, template) {
|
|
|
513
477
|
` source: ${registry.source}\n` +
|
|
514
478
|
(registry.template ? ` template: ${registry.template}\n` : ""));
|
|
515
479
|
}
|
|
480
|
+
/**
|
|
481
|
+
* Preserve top-level YAML sections not declared by the Trellis template.
|
|
482
|
+
* Project extensions are identified by structure, not by Pennix-specific key
|
|
483
|
+
* names; `registry` remains handled by its existing normalized path.
|
|
484
|
+
*/
|
|
485
|
+
function preserveExistingConfigExtensions(cwd, template) {
|
|
486
|
+
const configPath = path.join(cwd, DIR_NAMES.WORKFLOW, "config.yaml");
|
|
487
|
+
if (!fs.existsSync(configPath)) {
|
|
488
|
+
return template;
|
|
489
|
+
}
|
|
490
|
+
const existing = fs.readFileSync(configPath, "utf-8");
|
|
491
|
+
const sectionPattern = /^([A-Za-z_][A-Za-z0-9_-]*):(?:\s|$)/;
|
|
492
|
+
const sections = (content) => {
|
|
493
|
+
const lines = content.split(/\r?\n/);
|
|
494
|
+
const starts = [];
|
|
495
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
496
|
+
const match = lines[index]?.match(sectionPattern);
|
|
497
|
+
if (match?.[1])
|
|
498
|
+
starts.push({ key: match[1], index });
|
|
499
|
+
}
|
|
500
|
+
return new Map(starts.map(({ key, index }, position) => [
|
|
501
|
+
key,
|
|
502
|
+
{
|
|
503
|
+
content: lines
|
|
504
|
+
.slice(index, starts[position + 1]?.index ?? lines.length)
|
|
505
|
+
.join("\n"),
|
|
506
|
+
index,
|
|
507
|
+
},
|
|
508
|
+
]));
|
|
509
|
+
};
|
|
510
|
+
const templateSections = sections(template);
|
|
511
|
+
const templateKeys = new Set(templateSections.keys());
|
|
512
|
+
const existingSections = sections(existing);
|
|
513
|
+
const templateLines = new Set(template.split(/\r?\n/));
|
|
514
|
+
const extensions = [...existingSections].filter(([key]) => key !== "registry" && !templateKeys.has(key));
|
|
515
|
+
const withRegistry = preserveExistingRegistryConfig(cwd, template);
|
|
516
|
+
if (extensions.length === 0)
|
|
517
|
+
return withRegistry;
|
|
518
|
+
// Keep project comments that introduce an extension, but do not duplicate
|
|
519
|
+
// the template's trailing documentation when the extension was appended
|
|
520
|
+
// directly after a generated section. The line-based parser intentionally
|
|
521
|
+
// stays limited to the generated top-level YAML shape.
|
|
522
|
+
const extensionContent = extensions.map(([, section]) => {
|
|
523
|
+
const preceding = existing
|
|
524
|
+
.split(/\r?\n/)
|
|
525
|
+
.slice(Math.max(0, section.index - 32), section.index);
|
|
526
|
+
const projectComments = preceding.filter((line) => line.trimStart().startsWith("#") && !templateLines.has(line));
|
|
527
|
+
const leading = projectComments.length > 0 ? `${projectComments.join("\n")}\n` : "";
|
|
528
|
+
return `${leading}${section.content.trimEnd()}`;
|
|
529
|
+
});
|
|
530
|
+
return `${withRegistry.trimEnd()}\n${extensionContent.join("\n\n")}\n`;
|
|
531
|
+
}
|
|
516
532
|
async function collectRegistrySpecTemplates(cwd) {
|
|
517
533
|
const config = loadSpecRegistryConfig(cwd);
|
|
518
534
|
if (!config)
|
|
@@ -589,16 +605,15 @@ bypassUpdateSkip = false) {
|
|
|
589
605
|
files.set(`${PATHS.AGENTS}/${agentFile}`, content);
|
|
590
606
|
}
|
|
591
607
|
// Configuration
|
|
592
|
-
files.set(`${DIR_NAMES.WORKFLOW}/config.yaml`,
|
|
608
|
+
files.set(`${DIR_NAMES.WORKFLOW}/config.yaml`, preserveExistingConfigExtensions(cwd, configYamlTemplate));
|
|
593
609
|
files.set(`${DIR_NAMES.WORKFLOW}/.gitignore`, gitignoreTemplate);
|
|
594
|
-
// workflow.md is
|
|
595
|
-
//
|
|
596
|
-
//
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
files.set(`${DIR_NAMES.WORKFLOW}/workflow.md`, workflowMdTemplate);
|
|
610
|
+
// workflow.md is runtime-parsed by get_context.py and shared hooks. Keep it
|
|
611
|
+
// on the normal whole-file update path, but resolve the project's selected
|
|
612
|
+
// workflow instead of silently substituting the bundled native workflow.
|
|
613
|
+
const selectedWorkflow = await resolveWorkflowUpdateTemplate(cwd, hashes);
|
|
614
|
+
if (selectedWorkflow !== null) {
|
|
615
|
+
files.set(`${DIR_NAMES.WORKFLOW}/workflow.md`, selectedWorkflow);
|
|
616
|
+
}
|
|
602
617
|
// workspace/index.md stays excluded — it's runtime-appended by add_session.py
|
|
603
618
|
// (journal index) and has no script-parsed structure.
|
|
604
619
|
files.set(FILE_NAMES.AGENTS, buildAgentsMdTemplate(cwd));
|
|
@@ -621,7 +636,7 @@ bypassUpdateSkip = false) {
|
|
|
621
636
|
// as a modified-file conflict by the hash comparison below.
|
|
622
637
|
if (platforms.has("codex")) {
|
|
623
638
|
preserveCodexAgentModelKeys(cwd, files);
|
|
624
|
-
|
|
639
|
+
preserveCodexProjectConfig(cwd, files);
|
|
625
640
|
}
|
|
626
641
|
preserveExistingClaudeStatusLine(cwd, files);
|
|
627
642
|
if (platforms.has("claude-code") && isClaudeStatuslineManaged(cwd, hashes)) {
|
|
@@ -648,6 +663,55 @@ bypassUpdateSkip = false) {
|
|
|
648
663
|
}
|
|
649
664
|
return files;
|
|
650
665
|
}
|
|
666
|
+
/**
|
|
667
|
+
* Resolve the workflow selected by this project for generic updates.
|
|
668
|
+
*
|
|
669
|
+
* Provenance is the durable source of truth for marketplace workflows. Older
|
|
670
|
+
* projects without provenance retain their historical behavior: hash-tracked
|
|
671
|
+
* workflow.md uses the bundled template, while an untracked custom workflow
|
|
672
|
+
* remains outside the generic update set.
|
|
673
|
+
*/
|
|
674
|
+
async function resolveWorkflowUpdateTemplate(cwd, hashes) {
|
|
675
|
+
const provenance = loadWorkflowProvenance(cwd);
|
|
676
|
+
if (!provenance) {
|
|
677
|
+
return hashes[PATHS.WORKFLOW_GUIDE_FILE] ? workflowMdTemplate : null;
|
|
678
|
+
}
|
|
679
|
+
const template = await resolveWorkflowTemplate(provenance.workflow_id, {
|
|
680
|
+
source: provenance.source_kind === "marketplace"
|
|
681
|
+
? provenance.registry
|
|
682
|
+
: undefined,
|
|
683
|
+
});
|
|
684
|
+
const nativeVersionTransition = template.id === NATIVE_WORKFLOW_ID &&
|
|
685
|
+
template.source === "bundled" &&
|
|
686
|
+
provenance.ref !== template.ref &&
|
|
687
|
+
compareVersions(provenance.ref, template.ref) !== null;
|
|
688
|
+
const mismatches = workflowProvenanceMismatches(provenance, template).filter((mismatch) => !(nativeVersionTransition &&
|
|
689
|
+
(mismatch === "ref" ||
|
|
690
|
+
(mismatch === "template integrity" &&
|
|
691
|
+
hashes[PATHS.WORKFLOW_GUIDE_FILE] === provenance.content_sha256))));
|
|
692
|
+
if (mismatches.length > 0) {
|
|
693
|
+
throw new Error(`Workflow provenance verification failed: ${mismatches.join(", ")}.`);
|
|
694
|
+
}
|
|
695
|
+
return template.id === NATIVE_WORKFLOW_ID
|
|
696
|
+
? workflowMdTemplate
|
|
697
|
+
: template.content;
|
|
698
|
+
}
|
|
699
|
+
function refreshNativeWorkflowProvenance(cwd) {
|
|
700
|
+
const record = loadWorkflowProvenance(cwd);
|
|
701
|
+
if (record?.source_kind !== "bundled" ||
|
|
702
|
+
record.workflow_id !== NATIVE_WORKFLOW_ID)
|
|
703
|
+
return;
|
|
704
|
+
const workflowPath = path.join(cwd, PATHS.WORKFLOW_GUIDE_FILE);
|
|
705
|
+
if (fs.existsSync(workflowPath) &&
|
|
706
|
+
fs.readFileSync(workflowPath, "utf-8") ===
|
|
707
|
+
replacePythonCommandLiterals(workflowMdTemplate)) {
|
|
708
|
+
writeWorkflowProvenance(cwd, {
|
|
709
|
+
...record,
|
|
710
|
+
ref: VERSION,
|
|
711
|
+
content_sha256: computeHash(workflowMdTemplate),
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
}
|
|
651
715
|
/**
|
|
652
716
|
* Analyze changes between current files and templates
|
|
653
717
|
*
|
|
@@ -687,11 +751,15 @@ function analyzeChanges(cwd, hashes, templates) {
|
|
|
687
751
|
}
|
|
688
752
|
else {
|
|
689
753
|
const existingContent = fs.readFileSync(fullPath, "utf-8");
|
|
690
|
-
const
|
|
691
|
-
|
|
692
|
-
|
|
754
|
+
const markers = relativePath === FILE_NAMES.AGENTS
|
|
755
|
+
? [TRELLIS_BLOCK_START, TRELLIS_BLOCK_END]
|
|
756
|
+
: relativePath === COPILOT_INSTRUCTIONS_PATH
|
|
757
|
+
? [COPILOT_INSTRUCTIONS_BLOCK_START, COPILOT_INSTRUCTIONS_BLOCK_END]
|
|
758
|
+
: null;
|
|
759
|
+
if (markers &&
|
|
760
|
+
mergeManagedMarkdownBlock(existingContent, newContent, markers[0], markers[1]) === null) {
|
|
693
761
|
change.status = "changed";
|
|
694
|
-
result.
|
|
762
|
+
result.changedFiles.push(change);
|
|
695
763
|
continue;
|
|
696
764
|
}
|
|
697
765
|
if (existingContent === newContent) {
|
|
@@ -713,6 +781,12 @@ function analyzeChanges(cwd, hashes, templates) {
|
|
|
713
781
|
change.status = "changed";
|
|
714
782
|
result.autoUpdateFiles.push(change);
|
|
715
783
|
}
|
|
784
|
+
else if (isManagedMarkdownBlockOnlyChange(relativePath, existingContent, newContent)) {
|
|
785
|
+
// The project-owned prose around AGENTS.md is unchanged; only the
|
|
786
|
+
// explicitly managed Trellis block moved to a new template version.
|
|
787
|
+
change.status = "changed";
|
|
788
|
+
result.autoUpdateFiles.push(change);
|
|
789
|
+
}
|
|
716
790
|
else {
|
|
717
791
|
// Hash differs (or no stored hash) - user modified the file
|
|
718
792
|
// Needs confirmation
|
|
@@ -955,6 +1029,7 @@ export function shouldExcludeFromBackup(relativePath) {
|
|
|
955
1029
|
*/
|
|
956
1030
|
function createFullBackup(cwd) {
|
|
957
1031
|
const backupDir = createBackupDirPath(cwd);
|
|
1032
|
+
const stagingDir = `${backupDir}.incomplete`;
|
|
958
1033
|
let hasFiles = false;
|
|
959
1034
|
for (const dir of BACKUP_DIRS) {
|
|
960
1035
|
const dirPath = path.join(cwd, dir);
|
|
@@ -968,10 +1043,10 @@ function createFullBackup(cwd) {
|
|
|
968
1043
|
continue;
|
|
969
1044
|
// Create backup
|
|
970
1045
|
if (!hasFiles) {
|
|
971
|
-
fs.mkdirSync(
|
|
1046
|
+
fs.mkdirSync(stagingDir, { recursive: true });
|
|
972
1047
|
hasFiles = true;
|
|
973
1048
|
}
|
|
974
|
-
backupFile(cwd,
|
|
1049
|
+
backupFile(cwd, stagingDir, relativePath);
|
|
975
1050
|
}
|
|
976
1051
|
}
|
|
977
1052
|
for (const relativePath of BACKUP_FILES) {
|
|
@@ -981,10 +1056,14 @@ function createFullBackup(cwd) {
|
|
|
981
1056
|
if (shouldExcludeFromBackup(relativePath))
|
|
982
1057
|
continue;
|
|
983
1058
|
if (!hasFiles) {
|
|
984
|
-
fs.mkdirSync(
|
|
1059
|
+
fs.mkdirSync(stagingDir, { recursive: true });
|
|
985
1060
|
hasFiles = true;
|
|
986
1061
|
}
|
|
987
|
-
backupFile(cwd,
|
|
1062
|
+
backupFile(cwd, stagingDir, relativePath);
|
|
1063
|
+
}
|
|
1064
|
+
if (hasFiles) {
|
|
1065
|
+
fs.writeFileSync(path.join(stagingDir, BACKUP_COMPLETE_MARKER), VERSION, "utf8");
|
|
1066
|
+
fs.renameSync(stagingDir, backupDir);
|
|
988
1067
|
}
|
|
989
1068
|
return hasFiles ? backupDir : null;
|
|
990
1069
|
}
|
|
@@ -1224,7 +1303,9 @@ export function classifyMigrations(migrations, cwd, hashes, templates) {
|
|
|
1224
1303
|
const newExists = fs.existsSync(newPath);
|
|
1225
1304
|
if (newExists) {
|
|
1226
1305
|
// Target exists - check if it only contains unmodified template files
|
|
1227
|
-
if (isDirectorySafeToReplace(cwd, item.to, hashes, templates)
|
|
1306
|
+
if (isDirectorySafeToReplace(cwd, item.to, hashes, templates) &&
|
|
1307
|
+
(!dirMatchesCurrentTemplates(cwd, item.to, templates) ||
|
|
1308
|
+
isDirectorySafeToReplace(cwd, item.from, hashes, templates))) {
|
|
1228
1309
|
// Safe to delete target and rename source
|
|
1229
1310
|
result.auto.push(item);
|
|
1230
1311
|
}
|
|
@@ -1451,6 +1532,11 @@ export async function executeMigrations(classified, cwd, options, templates) {
|
|
|
1451
1532
|
// flavored bytes, so just drop the now-redundant source instead (#447).
|
|
1452
1533
|
if (fs.existsSync(newPath) &&
|
|
1453
1534
|
dirMatchesCurrentTemplates(cwd, item.to, templates)) {
|
|
1535
|
+
if (!isDirectorySafeToReplace(cwd, item.from, loadHashes(cwd), templates)) {
|
|
1536
|
+
result.skipped++;
|
|
1537
|
+
console.log(chalk.yellow(" ○ Preserved modified migration source: " + item.from));
|
|
1538
|
+
continue;
|
|
1539
|
+
}
|
|
1454
1540
|
removeDirectoryRecursive(oldPath);
|
|
1455
1541
|
const hashes = loadHashes(cwd);
|
|
1456
1542
|
const updatedHashes = {};
|
|
@@ -1633,6 +1719,7 @@ export async function update(options) {
|
|
|
1633
1719
|
}
|
|
1634
1720
|
console.log(chalk.cyan("\nTrellis Update"));
|
|
1635
1721
|
console.log(chalk.cyan("══════════════\n"));
|
|
1722
|
+
reportAgentsOverride(cwd);
|
|
1636
1723
|
// Set up proxy before any network calls (npm version check)
|
|
1637
1724
|
setupProxy();
|
|
1638
1725
|
// Get versions
|
|
@@ -1865,6 +1952,8 @@ export async function update(options) {
|
|
|
1865
1952
|
}
|
|
1866
1953
|
if (zcodeConfigured)
|
|
1867
1954
|
printZcodeSetupHint();
|
|
1955
|
+
if (!options.dryRun)
|
|
1956
|
+
refreshNativeWorkflowProvenance(cwd);
|
|
1868
1957
|
return;
|
|
1869
1958
|
}
|
|
1870
1959
|
// Show what this operation will do
|
|
@@ -1928,277 +2017,301 @@ export async function update(options) {
|
|
|
1928
2017
|
return;
|
|
1929
2018
|
}
|
|
1930
2019
|
}
|
|
1931
|
-
//
|
|
1932
|
-
const
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
safeDeleted = executeSafeFileDeletes(safeFileDeletes, cwd);
|
|
1961
|
-
if (safeDeleted > 0) {
|
|
1962
|
-
console.log(chalk.cyan(`\nCleaned up ${safeDeleted} deprecated command file(s)`));
|
|
2020
|
+
// Keep the rollback snapshot protected until all update writes finish.
|
|
2021
|
+
const backupLockPath = path.join(cwd, DIR_NAMES.WORKFLOW, BACKUP_PRUNE_LOCK);
|
|
2022
|
+
const backupLock = fs.openSync(backupLockPath, "wx");
|
|
2023
|
+
try {
|
|
2024
|
+
// Create complete backup of all managed platform/workflow directories
|
|
2025
|
+
const backupDir = createFullBackup(cwd);
|
|
2026
|
+
if (backupDir) {
|
|
2027
|
+
console.log(chalk.gray(`\nBackup created: ${path.relative(cwd, backupDir)}/`));
|
|
2028
|
+
}
|
|
2029
|
+
// Execute migrations if --migrate flag is set
|
|
2030
|
+
if (options.migrate && classifiedMigrations) {
|
|
2031
|
+
const migrationResult = await executeMigrations(classifiedMigrations, cwd, {
|
|
2032
|
+
force: options.force,
|
|
2033
|
+
skipAll: options.skipAll,
|
|
2034
|
+
}, templates);
|
|
2035
|
+
printMigrationResult(migrationResult);
|
|
2036
|
+
// Hardcoded: Rename traces-*.md to journal-*.md in workspace directories
|
|
2037
|
+
// Why hardcoded: The migration system only supports fixed path renames, not pattern-based.
|
|
2038
|
+
// traces-*.md files are in .trellis/workspace/{developer}/ with variable developer names
|
|
2039
|
+
// and variable file numbers (traces-1.md, traces-2.md, etc.), so we can't enumerate them
|
|
2040
|
+
// in the migration manifest. This is a one-time migration for the 0.2.0 naming redesign.
|
|
2041
|
+
const workspaceDir = path.join(cwd, PATHS.WORKSPACE);
|
|
2042
|
+
const { renamed: journalRenamed, skipped: journalSkipped } = renameTracesToJournal(workspaceDir);
|
|
2043
|
+
if (journalRenamed > 0) {
|
|
2044
|
+
console.log(chalk.cyan(`Renamed ${journalRenamed} traces file(s) to journal`));
|
|
2045
|
+
}
|
|
2046
|
+
for (const oldPath of journalSkipped) {
|
|
2047
|
+
console.warn(chalk.yellow(`Kept ${path.relative(cwd, oldPath)}: its journal target already exists`));
|
|
2048
|
+
}
|
|
1963
2049
|
}
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
let createdNew = 0;
|
|
1971
|
-
// Add new files
|
|
1972
|
-
if (changes.newFiles.length > 0) {
|
|
1973
|
-
console.log(chalk.blue("\nAdding new files..."));
|
|
1974
|
-
for (const file of changes.newFiles) {
|
|
1975
|
-
const dir = path.dirname(file.path);
|
|
1976
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
1977
|
-
fs.writeFileSync(file.path, file.newContent);
|
|
1978
|
-
// Make scripts executable
|
|
1979
|
-
if (file.relativePath.endsWith(".sh") ||
|
|
1980
|
-
file.relativePath.endsWith(".py")) {
|
|
1981
|
-
fs.chmodSync(file.path, "755");
|
|
2050
|
+
// Execute safe-file-delete (after backup, before template writes)
|
|
2051
|
+
let safeDeleted = 0;
|
|
2052
|
+
if (hasSafeDeletes) {
|
|
2053
|
+
safeDeleted = executeSafeFileDeletes(safeFileDeletes, cwd);
|
|
2054
|
+
if (safeDeleted > 0) {
|
|
2055
|
+
console.log(chalk.cyan(`\nCleaned up ${safeDeleted} deprecated command file(s)`));
|
|
1982
2056
|
}
|
|
1983
|
-
console.log(chalk.green(` + ${file.relativePath}`));
|
|
1984
|
-
added++;
|
|
1985
2057
|
}
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
2058
|
+
// Track results
|
|
2059
|
+
let added = 0;
|
|
2060
|
+
let autoUpdated = 0;
|
|
2061
|
+
let updated = 0;
|
|
2062
|
+
let skipped = 0;
|
|
2063
|
+
let createdNew = 0;
|
|
2064
|
+
// Add new files
|
|
2065
|
+
if (changes.newFiles.length > 0) {
|
|
2066
|
+
console.log(chalk.blue("\nAdding new files..."));
|
|
2067
|
+
for (const file of changes.newFiles) {
|
|
2068
|
+
const dir = path.dirname(file.path);
|
|
2069
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
2070
|
+
fs.writeFileSync(file.path, file.newContent);
|
|
2071
|
+
// Make scripts executable
|
|
2072
|
+
if (file.relativePath.endsWith(".sh") ||
|
|
2073
|
+
file.relativePath.endsWith(".py")) {
|
|
2074
|
+
fs.chmodSync(file.path, "755");
|
|
2075
|
+
}
|
|
2076
|
+
console.log(chalk.green(` + ${file.relativePath}`));
|
|
2077
|
+
added++;
|
|
1996
2078
|
}
|
|
1997
|
-
console.log(chalk.cyan(` ↑ ${file.relativePath}`));
|
|
1998
|
-
autoUpdated++;
|
|
1999
2079
|
}
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
const applyToAll = { action: null };
|
|
2005
|
-
for (const file of changes.changedFiles) {
|
|
2006
|
-
const action = await promptConflictResolution(file, options, applyToAll);
|
|
2007
|
-
if (action === "overwrite") {
|
|
2080
|
+
// Auto-update files (template updated, user didn't modify)
|
|
2081
|
+
if (changes.autoUpdateFiles.length > 0) {
|
|
2082
|
+
console.log(chalk.blue("\nAuto-updating template files..."));
|
|
2083
|
+
for (const file of changes.autoUpdateFiles) {
|
|
2008
2084
|
fs.writeFileSync(file.path, file.newContent);
|
|
2085
|
+
// Make scripts executable
|
|
2009
2086
|
if (file.relativePath.endsWith(".sh") ||
|
|
2010
2087
|
file.relativePath.endsWith(".py")) {
|
|
2011
2088
|
fs.chmodSync(file.path, "755");
|
|
2012
2089
|
}
|
|
2013
|
-
console.log(chalk.
|
|
2014
|
-
|
|
2090
|
+
console.log(chalk.cyan(` ↑ ${file.relativePath}`));
|
|
2091
|
+
autoUpdated++;
|
|
2015
2092
|
}
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2093
|
+
}
|
|
2094
|
+
// Handle changed files
|
|
2095
|
+
if (changes.changedFiles.length > 0) {
|
|
2096
|
+
console.log(chalk.blue("\n--- Resolving conflicts ---\n"));
|
|
2097
|
+
const applyToAll = { action: null };
|
|
2098
|
+
for (const file of changes.changedFiles) {
|
|
2099
|
+
const action = await promptConflictResolution(file, options, applyToAll);
|
|
2100
|
+
if (action === "overwrite") {
|
|
2101
|
+
const markers = file.relativePath === FILE_NAMES.AGENTS
|
|
2102
|
+
? [TRELLIS_BLOCK_START, TRELLIS_BLOCK_END]
|
|
2103
|
+
: file.relativePath === COPILOT_INSTRUCTIONS_PATH
|
|
2104
|
+
? [
|
|
2105
|
+
COPILOT_INSTRUCTIONS_BLOCK_START,
|
|
2106
|
+
COPILOT_INSTRUCTIONS_BLOCK_END,
|
|
2107
|
+
]
|
|
2108
|
+
: null;
|
|
2109
|
+
if (markers &&
|
|
2110
|
+
mergeManagedMarkdownBlock(fs.readFileSync(file.path, "utf-8"), file.newContent, markers[0], markers[1]) === null) {
|
|
2111
|
+
throw new Error("Refusing ambiguous managed Markdown markers in " +
|
|
2112
|
+
file.relativePath +
|
|
2113
|
+
"; use --create-new or repair the markers.");
|
|
2114
|
+
}
|
|
2115
|
+
fs.writeFileSync(file.path, file.newContent);
|
|
2116
|
+
if (file.relativePath.endsWith(".sh") ||
|
|
2117
|
+
file.relativePath.endsWith(".py")) {
|
|
2118
|
+
fs.chmodSync(file.path, "755");
|
|
2119
|
+
}
|
|
2120
|
+
console.log(chalk.yellow(` ✓ Overwritten: ${file.relativePath}`));
|
|
2121
|
+
updated++;
|
|
2122
|
+
}
|
|
2123
|
+
else if (action === "create-new") {
|
|
2124
|
+
const newPath = file.path + ".new";
|
|
2125
|
+
fs.writeFileSync(newPath, file.newContent);
|
|
2126
|
+
console.log(chalk.blue(` ✓ Created: ${file.relativePath}.new`));
|
|
2127
|
+
createdNew++;
|
|
2128
|
+
}
|
|
2129
|
+
else {
|
|
2130
|
+
console.log(chalk.gray(` ○ Skipped: ${file.relativePath}`));
|
|
2131
|
+
skipped++;
|
|
2132
|
+
}
|
|
2021
2133
|
}
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2134
|
+
}
|
|
2135
|
+
// Append additive config.yaml sections introduced between versions.
|
|
2136
|
+
// Sentinel-gated, so users keep their customizations and re-running update
|
|
2137
|
+
// on already-migrated files is a no-op. Skipped on unknown / downgrade.
|
|
2138
|
+
let configSectionsAppended = 0;
|
|
2139
|
+
if (cliVsProject > 0 && projectVersion !== "unknown") {
|
|
2140
|
+
const sectionEntries = getConfigSectionsAddedBetween(projectVersion, cliVersion);
|
|
2141
|
+
if (sectionEntries.length > 0) {
|
|
2142
|
+
const { appended } = applyConfigSectionsAdded(sectionEntries, cwd, templates);
|
|
2143
|
+
configSectionsAppended = appended;
|
|
2025
2144
|
}
|
|
2026
2145
|
}
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
for (const file of changes.autoUpdateFiles) {
|
|
2048
|
-
filesToHash.set(file.relativePath, file.newContent);
|
|
2049
|
-
}
|
|
2050
|
-
// Only hash overwritten files (not skipped or .new copies)
|
|
2051
|
-
for (const file of changes.changedFiles) {
|
|
2052
|
-
const fullPath = path.join(cwd, file.relativePath);
|
|
2053
|
-
if (fs.existsSync(fullPath)) {
|
|
2054
|
-
const content = fs.readFileSync(fullPath, "utf-8");
|
|
2055
|
-
if (content === file.newContent) {
|
|
2056
|
-
filesToHash.set(file.relativePath, file.newContent);
|
|
2146
|
+
// Update version file
|
|
2147
|
+
updateVersionFile(cwd);
|
|
2148
|
+
refreshNativeWorkflowProvenance(cwd);
|
|
2149
|
+
// Update template hashes for new, auto-updated, and overwritten files
|
|
2150
|
+
const filesToHash = new Map(unchangedFileHashRepairs);
|
|
2151
|
+
for (const file of changes.newFiles) {
|
|
2152
|
+
filesToHash.set(file.relativePath, file.newContent);
|
|
2153
|
+
}
|
|
2154
|
+
// Auto-updated files always get new hash
|
|
2155
|
+
for (const file of changes.autoUpdateFiles) {
|
|
2156
|
+
filesToHash.set(file.relativePath, file.newContent);
|
|
2157
|
+
}
|
|
2158
|
+
// Only hash overwritten files (not skipped or .new copies)
|
|
2159
|
+
for (const file of changes.changedFiles) {
|
|
2160
|
+
const fullPath = path.join(cwd, file.relativePath);
|
|
2161
|
+
if (fs.existsSync(fullPath)) {
|
|
2162
|
+
const content = fs.readFileSync(fullPath, "utf-8");
|
|
2163
|
+
if (content === file.newContent) {
|
|
2164
|
+
filesToHash.set(file.relativePath, file.newContent);
|
|
2165
|
+
}
|
|
2057
2166
|
}
|
|
2058
2167
|
}
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2168
|
+
if (filesToHash.size > 0) {
|
|
2169
|
+
updateHashes(cwd, filesToHash);
|
|
2170
|
+
}
|
|
2171
|
+
// Print summary
|
|
2172
|
+
console.log(chalk.cyan("\n--- Summary ---\n"));
|
|
2173
|
+
if (added > 0) {
|
|
2174
|
+
console.log(` Added: ${added} file(s)`);
|
|
2175
|
+
}
|
|
2176
|
+
if (autoUpdated > 0) {
|
|
2177
|
+
console.log(` Auto-updated: ${autoUpdated} file(s)`);
|
|
2178
|
+
}
|
|
2179
|
+
if (updated > 0) {
|
|
2180
|
+
console.log(` Updated: ${updated} file(s)`);
|
|
2181
|
+
}
|
|
2182
|
+
if (skipped > 0) {
|
|
2183
|
+
console.log(` Skipped: ${skipped} file(s)`);
|
|
2184
|
+
}
|
|
2185
|
+
if (createdNew > 0) {
|
|
2186
|
+
console.log(` Created .new copies: ${createdNew} file(s)`);
|
|
2187
|
+
}
|
|
2188
|
+
if (safeDeleted > 0) {
|
|
2189
|
+
console.log(` Cleaned up: ${safeDeleted} deprecated file(s)`);
|
|
2190
|
+
}
|
|
2191
|
+
if (configSectionsAppended > 0) {
|
|
2192
|
+
console.log(` Config sections added: ${configSectionsAppended}`);
|
|
2193
|
+
}
|
|
2194
|
+
if (backupDir) {
|
|
2195
|
+
console.log(` Backup: ${path.relative(cwd, backupDir)}/`);
|
|
2196
|
+
}
|
|
2197
|
+
const actionWord = isDowngrade ? "Downgrade" : "Update";
|
|
2198
|
+
console.log(chalk.green(`\n✅ ${actionWord} complete! (${projectVersion} → ${cliVersion})`));
|
|
2199
|
+
if (createdNew > 0) {
|
|
2200
|
+
console.log(chalk.gray("\nTip: Review .new files and merge changes manually if needed."));
|
|
2201
|
+
}
|
|
2202
|
+
// Create migration task if there are breaking changes with migration guides
|
|
2203
|
+
if (cliVsProject > 0 && projectVersion !== "unknown") {
|
|
2204
|
+
const metadata = getMigrationMetadata(projectVersion, cliVersion);
|
|
2205
|
+
if (metadata.breaking && metadata.migrationGuides.length > 0) {
|
|
2206
|
+
// Create task directory
|
|
2207
|
+
const today = new Date();
|
|
2208
|
+
const monthDay = `${String(today.getMonth() + 1).padStart(2, "0")}-${String(today.getDate()).padStart(2, "0")}`;
|
|
2209
|
+
const taskSlug = `migrate-to-${cliVersion}`;
|
|
2210
|
+
const taskDirName = `${monthDay}-${taskSlug}`;
|
|
2211
|
+
const tasksDir = path.join(cwd, DIR_NAMES.WORKFLOW, DIR_NAMES.TASKS);
|
|
2212
|
+
const taskDir = path.join(tasksDir, taskDirName);
|
|
2213
|
+
// Check if task already exists
|
|
2214
|
+
if (!fs.existsSync(taskDir)) {
|
|
2215
|
+
fs.mkdirSync(taskDir, { recursive: true });
|
|
2216
|
+
// Get current developer for assignee.
|
|
2217
|
+
// `.developer` is a key=value file (written by init_developer.py):
|
|
2218
|
+
// name=<developer-name>
|
|
2219
|
+
// initialized_at=<iso8601>
|
|
2220
|
+
// Reading it raw and .trim()-ing embeds the entire file contents
|
|
2221
|
+
// (including the `name=` prefix and the `initialized_at` line) into
|
|
2222
|
+
// the assignee field, producing bogus assignees like
|
|
2223
|
+
// "name=suyuan\ninitialized_at=2026-04-07T23:41:21.978312" that
|
|
2224
|
+
// later break session-start task rendering.
|
|
2225
|
+
const developerFile = path.join(cwd, DIR_NAMES.WORKFLOW, ".developer");
|
|
2226
|
+
let currentDeveloper = "unknown";
|
|
2227
|
+
if (fs.existsSync(developerFile)) {
|
|
2228
|
+
const raw = fs.readFileSync(developerFile, "utf-8");
|
|
2229
|
+
const nameMatch = raw.match(/^\s*name\s*=\s*(.+?)\s*$/m);
|
|
2230
|
+
if (nameMatch) {
|
|
2231
|
+
currentDeveloper = nameMatch[1];
|
|
2232
|
+
}
|
|
2124
2233
|
}
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
prdContent += guide;
|
|
2154
|
-
prdContent += "\n\n";
|
|
2155
|
-
if (aiInstructions) {
|
|
2156
|
-
prdContent += `### AI Assistant Instructions\n\n`;
|
|
2157
|
-
prdContent += `When helping with this migration:\n\n`;
|
|
2158
|
-
prdContent += aiInstructions;
|
|
2234
|
+
// Build task.json — canonical 24-field shape via shared factory.
|
|
2235
|
+
const taskTitle = `Migrate to v${cliVersion}`;
|
|
2236
|
+
const todayStr = today.toISOString().split("T")[0];
|
|
2237
|
+
const taskJson = emptyTaskJson({
|
|
2238
|
+
id: taskSlug,
|
|
2239
|
+
name: taskSlug,
|
|
2240
|
+
title: taskTitle,
|
|
2241
|
+
description: `Breaking change migration from v${projectVersion} to v${cliVersion}`,
|
|
2242
|
+
status: "planning",
|
|
2243
|
+
scope: "migration",
|
|
2244
|
+
priority: "P1",
|
|
2245
|
+
creator: "trellis-update",
|
|
2246
|
+
assignee: currentDeveloper,
|
|
2247
|
+
createdAt: todayStr,
|
|
2248
|
+
});
|
|
2249
|
+
// Write task.json
|
|
2250
|
+
const taskJsonPath = path.join(taskDir, "task.json");
|
|
2251
|
+
fs.writeFileSync(taskJsonPath, JSON.stringify(taskJson, null, 2));
|
|
2252
|
+
// Build PRD content
|
|
2253
|
+
let prdContent = `# Migration Task: Upgrade to v${cliVersion}\n\n`;
|
|
2254
|
+
prdContent += `**Created**: ${todayStr}\n`;
|
|
2255
|
+
prdContent += `**From Version**: ${projectVersion}\n`;
|
|
2256
|
+
prdContent += `**To Version**: ${cliVersion}\n`;
|
|
2257
|
+
prdContent += `**Assignee**: ${currentDeveloper}\n\n`;
|
|
2258
|
+
prdContent += `## Status\n\n- [ ] Review migration guide\n- [ ] Update custom files\n- [ ] Run \`trellis update --migrate\`\n- [ ] Test workflows\n\n`;
|
|
2259
|
+
for (const { version, guide, aiInstructions, } of metadata.migrationGuides) {
|
|
2260
|
+
prdContent += `---\n\n## v${version} Migration Guide\n\n`;
|
|
2261
|
+
prdContent += guide;
|
|
2159
2262
|
prdContent += "\n\n";
|
|
2263
|
+
if (aiInstructions) {
|
|
2264
|
+
prdContent += `### AI Assistant Instructions\n\n`;
|
|
2265
|
+
prdContent += `When helping with this migration:\n\n`;
|
|
2266
|
+
prdContent += aiInstructions;
|
|
2267
|
+
prdContent += "\n\n";
|
|
2268
|
+
}
|
|
2160
2269
|
}
|
|
2270
|
+
// Write PRD
|
|
2271
|
+
const prdPath = path.join(taskDir, "prd.md");
|
|
2272
|
+
fs.writeFileSync(prdPath, prdContent);
|
|
2273
|
+
console.log("");
|
|
2274
|
+
console.log(chalk.bgCyan.black.bold(" 📋 MIGRATION TASK CREATED "));
|
|
2275
|
+
console.log(chalk.cyan(`A task has been created to help you complete the migration:`));
|
|
2276
|
+
console.log(chalk.white(` ${DIR_NAMES.WORKFLOW}/${DIR_NAMES.TASKS}/${taskDirName}/`));
|
|
2277
|
+
console.log("");
|
|
2278
|
+
console.log(chalk.gray("Use AI to help: Ask Claude/Cursor to read the task and fix your custom files."));
|
|
2161
2279
|
}
|
|
2162
|
-
// Write PRD
|
|
2163
|
-
const prdPath = path.join(taskDir, "prd.md");
|
|
2164
|
-
fs.writeFileSync(prdPath, prdContent);
|
|
2165
|
-
console.log("");
|
|
2166
|
-
console.log(chalk.bgCyan.black.bold(" 📋 MIGRATION TASK CREATED "));
|
|
2167
|
-
console.log(chalk.cyan(`A task has been created to help you complete the migration:`));
|
|
2168
|
-
console.log(chalk.white(` ${DIR_NAMES.WORKFLOW}/${DIR_NAMES.TASKS}/${taskDirName}/`));
|
|
2169
|
-
console.log("");
|
|
2170
|
-
console.log(chalk.gray("Use AI to help: Ask Claude/Cursor to read the task and fix your custom files."));
|
|
2171
2280
|
}
|
|
2172
2281
|
}
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
if (finalMetadata.breaking || finalMetadata.changelog.length > 0) {
|
|
2180
|
-
console.log("");
|
|
2181
|
-
console.log(chalk.cyan("═".repeat(60)));
|
|
2182
|
-
if (finalMetadata.breaking) {
|
|
2183
|
-
console.log(chalk.bgRed.white.bold(" ⚠️ BREAKING CHANGES ") +
|
|
2184
|
-
chalk.red.bold(" This update contains breaking changes!"));
|
|
2282
|
+
if (zcodeConfigured)
|
|
2283
|
+
printZcodeSetupHint();
|
|
2284
|
+
// Display breaking change warnings at the very end (so they don't scroll off screen)
|
|
2285
|
+
if (cliVsProject > 0 && projectVersion !== "unknown") {
|
|
2286
|
+
const finalMetadata = getMigrationMetadata(projectVersion, cliVersion);
|
|
2287
|
+
if (finalMetadata.breaking || finalMetadata.changelog.length > 0) {
|
|
2185
2288
|
console.log("");
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
console.log(
|
|
2289
|
+
console.log(chalk.cyan("═".repeat(60)));
|
|
2290
|
+
if (finalMetadata.breaking) {
|
|
2291
|
+
console.log(chalk.bgRed.white.bold(" ⚠️ BREAKING CHANGES ") +
|
|
2292
|
+
chalk.red.bold(" This update contains breaking changes!"));
|
|
2293
|
+
console.log("");
|
|
2191
2294
|
}
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2295
|
+
if (finalMetadata.changelog.length > 0) {
|
|
2296
|
+
console.log(chalk.cyan.bold("📋 What's Changed:"));
|
|
2297
|
+
for (const entry of finalMetadata.changelog) {
|
|
2298
|
+
console.log(chalk.white(` ${entry}`));
|
|
2299
|
+
}
|
|
2300
|
+
console.log("");
|
|
2301
|
+
}
|
|
2302
|
+
if (finalMetadata.recommendMigrate && !options.migrate) {
|
|
2303
|
+
console.log(chalk.bgGreen.black.bold(" 💡 RECOMMENDED ") +
|
|
2304
|
+
chalk.green.bold(" Run with --migrate to complete the migration"));
|
|
2305
|
+
console.log(chalk.gray(" This will remove legacy files and apply all changes."));
|
|
2306
|
+
console.log("");
|
|
2307
|
+
}
|
|
2308
|
+
console.log(chalk.cyan("═".repeat(60)));
|
|
2199
2309
|
}
|
|
2200
|
-
console.log(chalk.cyan("═".repeat(60)));
|
|
2201
2310
|
}
|
|
2202
2311
|
}
|
|
2312
|
+
finally {
|
|
2313
|
+
fs.closeSync(backupLock);
|
|
2314
|
+
fs.rmSync(backupLockPath, { force: true });
|
|
2315
|
+
}
|
|
2203
2316
|
}
|
|
2204
2317
|
//# sourceMappingURL=update.js.map
|