@fission-ai/openspec 1.14.2 → 1.15.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/README.md +13 -9
- package/dist/cli/index.js +47 -0
- package/dist/commands/change.js +2 -1
- package/dist/commands/feedback.js +33 -20
- package/dist/commands/install-standalone.d.ts +27 -0
- package/dist/commands/install-standalone.js +150 -0
- package/dist/commands/schema.js +10 -3
- package/dist/commands/spec.js +2 -10
- package/dist/commands/validate.js +11 -4
- package/dist/commands/workflow/instructions.js +15 -4
- package/dist/commands/workflow/new-change.js +12 -4
- package/dist/commands/workflow/status.js +5 -2
- package/dist/commands/workflow/templates.js +5 -2
- package/dist/core/artifact-graph/outputs.js +204 -29
- package/dist/core/artifact-graph/resolver.d.ts +6 -0
- package/dist/core/artifact-graph/resolver.js +11 -0
- package/dist/core/available-tools.d.ts +2 -1
- package/dist/core/available-tools.js +8 -28
- package/dist/core/completion-tip.d.ts +23 -0
- package/dist/core/completion-tip.js +4 -4
- package/dist/core/completions/command-registry.js +15 -0
- package/dist/core/completions/completion-provider.js +1 -1
- package/dist/core/config-schema.d.ts +1 -0
- package/dist/core/config-schema.js +1 -0
- package/dist/core/file-view.d.ts +13 -0
- package/dist/core/file-view.js +14 -0
- package/dist/core/github-copilot/cloud-agent.d.ts +10 -2
- package/dist/core/github-copilot/cloud-agent.js +13 -9
- package/dist/core/global-config.d.ts +4 -0
- package/dist/core/global-config.js +5 -1
- package/dist/core/legacy-cleanup.d.ts +11 -1
- package/dist/core/legacy-cleanup.js +15 -18
- package/dist/core/migration.d.ts +27 -1
- package/dist/core/migration.js +77 -29
- package/dist/core/profile-sync-drift.d.ts +4 -3
- package/dist/core/profile-sync-drift.js +19 -16
- package/dist/core/shared/tool-detection.d.ts +12 -4
- package/dist/core/shared/tool-detection.js +29 -27
- package/dist/core/shared-skill-target.d.ts +12 -5
- package/dist/core/shared-skill-target.js +34 -23
- package/dist/core/specs-apply.js +23 -5
- package/dist/core/standalone-tip.d.ts +6 -0
- package/dist/core/standalone-tip.js +38 -0
- package/dist/core/templates/workflows/continue-change.js +18 -12
- package/dist/core/templates/workflows/explore.js +2 -2
- package/dist/core/templates/workflows/ff-change.js +8 -6
- package/dist/core/templates/workflows/propose.js +8 -6
- package/dist/core/templates/workflows/verify-change.js +4 -4
- package/dist/core/update-plan.d.ts +127 -0
- package/dist/core/update-plan.js +390 -0
- package/dist/core/update.d.ts +45 -1
- package/dist/core/update.js +279 -137
- package/dist/core/validation/validator.js +3 -3
- package/dist/core/version-check.d.ts +8 -1
- package/dist/core/version-check.js +48 -5
- package/dist/utils/executable-path.d.ts +5 -0
- package/dist/utils/executable-path.js +44 -0
- package/dist/utils/file-system.d.ts +1 -1
- package/dist/utils/file-system.js +1 -1
- package/dist/utils/interactive.d.ts +15 -0
- package/dist/utils/interactive.js +18 -0
- package/dist/utils/spec-discovery.d.ts +4 -4
- package/dist/utils/spec-discovery.js +7 -17
- package/dist/utils/spec-path-containment.d.ts +17 -0
- package/dist/utils/spec-path-containment.js +122 -0
- package/package.json +8 -4
package/dist/core/specs-apply.js
CHANGED
|
@@ -15,6 +15,7 @@ import { MarkdownParser } from './parsers/markdown-parser.js';
|
|
|
15
15
|
import { MIN_PURPOSE_LENGTH, PURPOSE_PLACEHOLDER_PREFIX, PURPOSE_PLACEHOLDER_SUFFIX, } from './validation/constants.js';
|
|
16
16
|
import { discoverSpecFiles } from '../utils/spec-discovery.js';
|
|
17
17
|
import { FileSystemUtils } from '../utils/file-system.js';
|
|
18
|
+
import { assertSpecFileWithin } from '../utils/spec-path-containment.js';
|
|
18
19
|
import { matchLineEnding } from '../utils/line-endings.js';
|
|
19
20
|
function isLexicallyWithin(allowedDirectory, targetPath) {
|
|
20
21
|
const relative = path.relative(path.resolve(allowedDirectory), path.resolve(targetPath));
|
|
@@ -29,7 +30,7 @@ function resolveTrustedSpecPath(specsRoot, specPath, projectRoot) {
|
|
|
29
30
|
}
|
|
30
31
|
try {
|
|
31
32
|
// Preserve spec.md links that remain inside the overall specs tree.
|
|
32
|
-
|
|
33
|
+
assertSpecFileWithin(specsRoot, specPath);
|
|
33
34
|
const root = FileSystemUtils.canonicalizeExistingPath(specsRoot);
|
|
34
35
|
return {
|
|
35
36
|
root,
|
|
@@ -52,7 +53,7 @@ function resolveTrustedSpecPath(specsRoot, specPath, projectRoot) {
|
|
|
52
53
|
process.emitWarning(`Capability '${path.basename(path.dirname(specPath))}' links outside the project; writing to ${root}`, 'OpenSpecExternalSpecWrite');
|
|
53
54
|
}
|
|
54
55
|
const file = path.join(root, path.basename(specPath));
|
|
55
|
-
|
|
56
|
+
assertSpecFileWithin(root, file);
|
|
56
57
|
return { root, file };
|
|
57
58
|
}
|
|
58
59
|
}
|
|
@@ -60,7 +61,7 @@ function assertTrustedSpecPath(root, specPath) {
|
|
|
60
61
|
if (FileSystemUtils.canonicalizeExistingPath(root) !== path.resolve(root)) {
|
|
61
62
|
throw new Error(`Path is outside the allowed directory: ${specPath}`);
|
|
62
63
|
}
|
|
63
|
-
|
|
64
|
+
assertSpecFileWithin(root, specPath);
|
|
64
65
|
}
|
|
65
66
|
// -----------------------------------------------------------------------------
|
|
66
67
|
// Public API
|
|
@@ -1042,6 +1043,14 @@ export async function pruneEmptyDirs(startDir, boundaryDir) {
|
|
|
1042
1043
|
*/
|
|
1043
1044
|
export async function writeUpdatedSpec(update, rebuilt, counts, options = {}) {
|
|
1044
1045
|
assertTrustedSpecPath(update.targetRoot, update.target);
|
|
1046
|
+
// Pin what the assertion just checked. A capability's spec.md may be a link
|
|
1047
|
+
// to the file the project keeps elsewhere, so the write follows a link by
|
|
1048
|
+
// design - and resolving it once, here, is what keeps that from becoming a
|
|
1049
|
+
// redirect: a link repointed after this line, including from inside
|
|
1050
|
+
// `beforeMutate`, cannot send the write to a file nothing verified. The
|
|
1051
|
+
// bytes still land in the same place a path-based write would have put them,
|
|
1052
|
+
// which keeps the in-place semantics below intact.
|
|
1053
|
+
const verifiedTarget = FileSystemUtils.canonicalizeExistingPath(update.target);
|
|
1045
1054
|
// Create target directory if needed
|
|
1046
1055
|
const targetDir = path.dirname(update.target);
|
|
1047
1056
|
await fs.mkdir(targetDir, { recursive: true });
|
|
@@ -1051,17 +1060,26 @@ export async function writeUpdatedSpec(update, rebuilt, counts, options = {}) {
|
|
|
1051
1060
|
// requirement moved. A spec that does not exist yet stays LF.
|
|
1052
1061
|
// Only a missing file means "no convention to match". Swallowing every error
|
|
1053
1062
|
// would read an existing but unreadable spec as absent and rewrite it as LF.
|
|
1054
|
-
const previous = await fs.readFile(
|
|
1063
|
+
const previous = await fs.readFile(verifiedTarget, 'utf-8').catch((error) => {
|
|
1055
1064
|
if (error.code === 'ENOENT')
|
|
1056
1065
|
return undefined;
|
|
1057
1066
|
throw error;
|
|
1058
1067
|
});
|
|
1059
1068
|
const toWrite = previous === undefined ? rebuilt : matchLineEnding(rebuilt, previous);
|
|
1060
1069
|
await options.beforeMutate?.();
|
|
1070
|
+
// `beforeMutate` awaits arbitrary work, so it is the widest gap between the
|
|
1071
|
+
// check above and the write below. Re-check there, and refuse unless the
|
|
1072
|
+
// target still resolves to the very file that was verified: pinning a
|
|
1073
|
+
// pathname is not enough on its own, because the pinned path can itself be
|
|
1074
|
+
// replaced by a link to somewhere else.
|
|
1075
|
+
assertTrustedSpecPath(update.targetRoot, update.target);
|
|
1076
|
+
if (FileSystemUtils.canonicalizeExistingPath(update.target) !== verifiedTarget) {
|
|
1077
|
+
throw new Error(`Spec target for '${update.id}' changed before it could be written: ${update.target}`);
|
|
1078
|
+
}
|
|
1061
1079
|
// Preserve the established in-place write semantics: symlink referents,
|
|
1062
1080
|
// hard-linked specs, ACLs, extended attributes, and filesystems without hard
|
|
1063
1081
|
// links must continue to behave as they did before capability retirement.
|
|
1064
|
-
await fs.writeFile(
|
|
1082
|
+
await fs.writeFile(verifiedTarget, toWrite);
|
|
1065
1083
|
if (options.silent)
|
|
1066
1084
|
return;
|
|
1067
1085
|
const specName = update.id;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const STANDALONE_TIP_MESSAGE = "Tip: OpenSpec also comes as a standalone app that runs without Node.js. To switch, run 'openspec install'.";
|
|
2
|
+
/** Never throws: a hint must not break a command. */
|
|
3
|
+
export declare function maybeShowStandaloneTip(options?: {
|
|
4
|
+
silent?: boolean;
|
|
5
|
+
}): Promise<void>;
|
|
6
|
+
//# sourceMappingURL=standalone-tip.d.ts.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-time note telling package-manager users that the standalone install
|
|
3
|
+
* exists. It only offers: people who chose npm may have a reason, so it is
|
|
4
|
+
* shown once, never repeated, and never changes anything.
|
|
5
|
+
*
|
|
6
|
+
* Shown on stderr after `openspec update` (the moment people think about
|
|
7
|
+
* versions), and only for a global package-manager install. Deferred, not
|
|
8
|
+
* consumed, when nobody would read it: stderr that is not a terminal (agents,
|
|
9
|
+
* pipes) and CI.
|
|
10
|
+
*/
|
|
11
|
+
import { isCiEnvironment } from '../utils/ci.js';
|
|
12
|
+
import { markConfigFlag, readRawConfig } from './completion-tip.js';
|
|
13
|
+
import { getCliInstallInfo, getStandaloneBinaryPath } from './version-check.js';
|
|
14
|
+
export const STANDALONE_TIP_MESSAGE = "Tip: OpenSpec also comes as a standalone app that runs without Node.js. To switch, run 'openspec install'.";
|
|
15
|
+
/** Never throws: a hint must not break a command. */
|
|
16
|
+
export async function maybeShowStandaloneTip(options = {}) {
|
|
17
|
+
if (isCiEnvironment() || getStandaloneBinaryPath()) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const raw = readRawConfig();
|
|
22
|
+
if (raw === null || raw.standaloneTipSeen === true || options.silent) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
// Project dependencies, npx runs and source checkouts have nothing to switch.
|
|
26
|
+
if (getCliInstallInfo().scope !== 'global') {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
// Record before printing: if the flag cannot be saved, staying quiet beats
|
|
30
|
+
// repeating the note on every run.
|
|
31
|
+
await markConfigFlag('standaloneTipSeen');
|
|
32
|
+
console.error(`\n${STANDALONE_TIP_MESSAGE}`);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// Silent failure - a hint should never break the CLI.
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=standalone-tip.js.map
|
|
@@ -14,8 +14,8 @@ const PLANNING_COMPLETE_HANDOFF = optionalWorkflow('apply', 'You can now impleme
|
|
|
14
14
|
*/
|
|
15
15
|
const PARTIAL_ARTIFACT_CHECK = `**Before anything else, finish a partly written artifact**:
|
|
16
16
|
- An artifact whose output is a glob pattern (e.g. \`specs/**/*.md\`) reads \`done\` as soon as one matching file exists, so a run interrupted while writing its files leaves it \`done\` with some still missing
|
|
17
|
-
- For each such \`done\` artifact, first get its instructions and read any completed dependencies needed to determine the expected files. Then compare those files with \`artifactPaths.<id>.existingOutputPaths\`. For spec-driven's \`specs\`, read the proposal and check for one \`specs/<capability-path>/spec.md\` per capability it lists; resolve each expected path against \`changeRoot\` before comparing path identity
|
|
18
|
-
- If any are missing, write only the missing files, and STOP - that counts as this invocation's ONE artifact. If you cannot tell whether a file was left out on purpose, ask the user`;
|
|
17
|
+
- For each such \`done\` artifact, first get its instructions and read any completed dependencies needed to determine the expected files. Then compare those files with \`artifactPaths.<id>.existingOutputPaths\`. For spec-driven's \`specs\`, read the proposal and check for one \`specs/<capability-path>/spec.md\` per capability it lists; resolve each expected path against \`changeRoot\` before comparing path identity, and verify each one remains inside \`changeRoot\` after resolving symlinks, both in its parent directories and in the file itself if it already exists
|
|
18
|
+
- Never write an expected path that fails that check; stop and tell the user. If any are missing, write only the missing files, each at its checked path - when the \`instruction\` field delegates creation to a skill or command, invoke it only if it can write each file to its checked path; otherwise stop and tell the user - and STOP - that counts as this invocation's ONE artifact. If you cannot tell whether a file was left out on purpose, ask the user`;
|
|
19
19
|
export function getContinueChangeSkillTemplate() {
|
|
20
20
|
return {
|
|
21
21
|
name: 'openspec-continue-change',
|
|
@@ -88,10 +88,13 @@ ${PROJECT_ROOT_GUARD}
|
|
|
88
88
|
- \`skipped\`/\`warning\`: present when the change declares skip_specs and this artifact must NOT be created - pick another artifact
|
|
89
89
|
- **Create the artifact file**:
|
|
90
90
|
- Read any completed dependency files for context - always re-read them from disk, even if you saw them earlier in the conversation (the user may have edited them)
|
|
91
|
-
- If the \`
|
|
92
|
-
-
|
|
93
|
-
-
|
|
94
|
-
|
|
91
|
+
- Choose the output path before creating anything. If the \`resolvedOutputPath\` specified in instructions is a glob pattern, choose a concrete path inside \`changeRoot\` using the schema instruction and the change's context, and verify it remains inside \`changeRoot\` after resolving symlinks, both in its parent directories and in the file itself if it already exists; if it does not, stop and tell the user. Otherwise the output path is \`resolvedOutputPath\`
|
|
92
|
+
- If the \`instruction\` field delegates creation to a specific skill or command, invoke it only if it can write the artifact to that output path; otherwise stop and tell the user
|
|
93
|
+
- Otherwise, write it yourself:
|
|
94
|
+
- Use \`template\` as the structure - fill in its sections
|
|
95
|
+
- Apply \`context\` and \`rules\` as constraints when writing - but do NOT copy them into the file
|
|
96
|
+
- Write the file to that output path
|
|
97
|
+
- Either way, verify the artifact file exists at that output path
|
|
95
98
|
- Show what was created and what's now unlocked
|
|
96
99
|
- STOP after creating ONE artifact
|
|
97
100
|
|
|
@@ -119,7 +122,7 @@ After each invocation, show:
|
|
|
119
122
|
|
|
120
123
|
The artifact types and their purpose depend on the schema. The \`instruction\` field from the instructions output is the authoritative guidance for each artifact - follow it even when the artifact has a familiar name (proposal.md, tasks.md, etc.), since custom schemas may define different content or a different process for the same file names.
|
|
121
124
|
|
|
122
|
-
If the \`instruction\` field directs you to use a specific skill or command to create the artifact, invoke it instead of writing the artifact directly.
|
|
125
|
+
If the \`instruction\` field directs you to use a specific skill or command to create the artifact, invoke it instead of writing the artifact directly - but only once you have chosen and checked the output path, and only if it can write there.
|
|
123
126
|
|
|
124
127
|
**Guardrails**
|
|
125
128
|
- Create ONE artifact per invocation
|
|
@@ -210,10 +213,13 @@ ${PROJECT_ROOT_GUARD}
|
|
|
210
213
|
- \`skipped\`/\`warning\`: present when the change declares skip_specs and this artifact must NOT be created - pick another artifact
|
|
211
214
|
- **Create the artifact file**:
|
|
212
215
|
- Read any completed dependency files for context - always re-read them from disk, even if you saw them earlier in the conversation (the user may have edited them)
|
|
213
|
-
- If the \`
|
|
214
|
-
-
|
|
215
|
-
-
|
|
216
|
-
|
|
216
|
+
- Choose the output path before creating anything. If the \`resolvedOutputPath\` specified in instructions is a glob pattern, choose a concrete path inside \`changeRoot\` using the schema instruction and the change's context, and verify it remains inside \`changeRoot\` after resolving symlinks, both in its parent directories and in the file itself if it already exists; if it does not, stop and tell the user. Otherwise the output path is \`resolvedOutputPath\`
|
|
217
|
+
- If the \`instruction\` field delegates creation to a specific skill or command, invoke it only if it can write the artifact to that output path; otherwise stop and tell the user
|
|
218
|
+
- Otherwise, write it yourself:
|
|
219
|
+
- Use \`template\` as the structure - fill in its sections
|
|
220
|
+
- Apply \`context\` and \`rules\` as constraints when writing - but do NOT copy them into the file
|
|
221
|
+
- Write the file to that output path
|
|
222
|
+
- Either way, verify the artifact file exists at that output path
|
|
217
223
|
- Show what was created and what's now unlocked
|
|
218
224
|
- STOP after creating ONE artifact
|
|
219
225
|
|
|
@@ -241,7 +247,7 @@ After each invocation, show:
|
|
|
241
247
|
|
|
242
248
|
The artifact types and their purpose depend on the schema. The \`instruction\` field from the instructions output is the authoritative guidance for each artifact - follow it even when the artifact has a familiar name (proposal.md, tasks.md, etc.), since custom schemas may define different content or a different process for the same file names.
|
|
243
249
|
|
|
244
|
-
If the \`instruction\` field directs you to use a specific skill or command to create the artifact, invoke it instead of writing the artifact directly.
|
|
250
|
+
If the \`instruction\` field directs you to use a specific skill or command to create the artifact, invoke it instead of writing the artifact directly - but only once you have chosen and checked the output path, and only if it can write there.
|
|
245
251
|
|
|
246
252
|
**Guardrails**
|
|
247
253
|
- Create ONE artifact per invocation
|
|
@@ -156,7 +156,7 @@ If the user asks you to capture the exploration as a new change, that request is
|
|
|
156
156
|
|
|
157
157
|
1. Run \`openspec new change "<name>"\` (with \`--store <id>\` when applicable) before creating any artifacts. Never create a new change directory under \`openspec/changes/\` by hand; the CLI scaffold creates required metadata such as \`.openspec.yaml\`. Keep the selected \`--store <id>\` on every applicable follow-up \`status\` and \`instructions\` command.
|
|
158
158
|
2. Run \`openspec status --change "<name>" --json\` (append the confirmed \`--store "<id>"\` only for a registered standalone store), then process the requested artifacts in dependency order. For each requested artifact that is \`ready\`, run \`openspec instructions "<artifact-id>" --change "<name>" --json\` (append the confirmed \`--store "<id>"\` only for a registered standalone store). Before creating a requested artifact, evaluate any condition in its own \`instruction\` against the explored change; record a deliberate skip instead when the condition does not apply. If a requested artifact is blocked by a direct prerequisite the user did not request, run \`openspec instructions "<prerequisite-id>" --change "<name>" --json\` (append the confirmed \`--store "<id>"\` only for a registered standalone store) for that prerequisite whether it is \`ready\` or \`blocked\`. If its own \`instruction\` states a condition, evaluate that condition against the explored change and record a deliberate skip only when the condition does not apply. If the condition applies, or the prerequisite is not conditional, treat it as a normal prerequisite and ask before expanding the capture. Do not create an unrequested prerequisite unless the user approves.
|
|
159
|
-
3. Follow the returned \`template\` and \`instruction\` fields. Read completed dependency files listed in \`dependencies\`, and apply \`context\` and \`rules\` as constraints without copying them into the artifact. If the instruction delegates creation to a specific skill or command, invoke it
|
|
159
|
+
3. Follow the returned \`template\` and \`instruction\` fields. Read completed dependency files listed in \`dependencies\`, and apply \`context\` and \`rules\` as constraints without copying them into the artifact. Choose the output path before creating anything: when \`resolvedOutputPath\` is a glob, use the instruction to choose a concrete path inside \`changeRoot\` and verify it remains inside \`changeRoot\` after resolving symlinks, both in its parent directories and in the file itself if it already exists; if it does not, stop and tell the user. Otherwise the output path is \`resolvedOutputPath\`. If the instruction delegates creation to a specific skill or command, invoke it only if it can write the artifact to that output path; otherwise stop and tell the user. Otherwise write the artifact to that output path. Verify that the selected concrete output exists at that output path.
|
|
160
160
|
4. After creating each artifact, re-run \`openspec status --change "<name>" --json\` (append the confirmed \`--store "<id>"\` only for a registered standalone store) and continue until every requested artifact is \`done\`, \`skipped\`, or was deliberately skipped because its own \`instruction\` stated a condition that did not apply. Tell the user about a deliberate conditional skip, remember it, and do not reconsider it. Dependencies are enablers, not gates: if a requested artifact is still \`blocked\` only because you deliberately skipped a conditional prerequisite, run \`openspec instructions "<artifact-id>" --change "<name>" --json\` (append the confirmed \`--store "<id>"\` only for a registered standalone store) despite the blocked status, then create it using step 3 only when those recorded conditional skips are its sole missing dependencies. If a requested artifact is blocked by a prerequisite the user did not ask to capture and cannot be conditionally skipped, explain that dependency and ask before expanding the capture.
|
|
161
161
|
|
|
162
162
|
Capture the artifact(s) the user requested without asking them to invoke another workflow command. If they asked only to start a change, stop after scaffolding and show its status. When the requested capture is done, stop there and name where the work continues: ${CAPTURE_PLANNING_HANDOFF}, and ${CAPTURE_APPLY_HANDOFF}. Capturing artifacts never starts implementing them.
|
|
@@ -491,7 +491,7 @@ If the user asks you to capture the exploration as a new change, that request is
|
|
|
491
491
|
|
|
492
492
|
1. Run \`openspec new change "<name>"\` (with \`--store <id>\` when applicable) before creating any artifacts. Never create a new change directory under \`openspec/changes/\` by hand; the CLI scaffold creates required metadata such as \`.openspec.yaml\`. Keep the selected \`--store <id>\` on every applicable follow-up \`status\` and \`instructions\` command.
|
|
493
493
|
2. Run \`openspec status --change "<name>" --json\` (append the confirmed \`--store "<id>"\` only for a registered standalone store), then process the requested artifacts in dependency order. For each requested artifact that is \`ready\`, run \`openspec instructions "<artifact-id>" --change "<name>" --json\` (append the confirmed \`--store "<id>"\` only for a registered standalone store). Before creating a requested artifact, evaluate any condition in its own \`instruction\` against the explored change; record a deliberate skip instead when the condition does not apply. If a requested artifact is blocked by a direct prerequisite the user did not request, run \`openspec instructions "<prerequisite-id>" --change "<name>" --json\` (append the confirmed \`--store "<id>"\` only for a registered standalone store) for that prerequisite whether it is \`ready\` or \`blocked\`. If its own \`instruction\` states a condition, evaluate that condition against the explored change and record a deliberate skip only when the condition does not apply. If the condition applies, or the prerequisite is not conditional, treat it as a normal prerequisite and ask before expanding the capture. Do not create an unrequested prerequisite unless the user approves.
|
|
494
|
-
3. Follow the returned \`template\` and \`instruction\` fields. Read completed dependency files listed in \`dependencies\`, and apply \`context\` and \`rules\` as constraints without copying them into the artifact. If the instruction delegates creation to a specific skill or command, invoke it
|
|
494
|
+
3. Follow the returned \`template\` and \`instruction\` fields. Read completed dependency files listed in \`dependencies\`, and apply \`context\` and \`rules\` as constraints without copying them into the artifact. Choose the output path before creating anything: when \`resolvedOutputPath\` is a glob, use the instruction to choose a concrete path inside \`changeRoot\` and verify it remains inside \`changeRoot\` after resolving symlinks, both in its parent directories and in the file itself if it already exists; if it does not, stop and tell the user. Otherwise the output path is \`resolvedOutputPath\`. If the instruction delegates creation to a specific skill or command, invoke it only if it can write the artifact to that output path; otherwise stop and tell the user. Otherwise write the artifact to that output path. Verify that the selected concrete output exists at that output path.
|
|
495
495
|
4. After creating each artifact, re-run \`openspec status --change "<name>" --json\` (append the confirmed \`--store "<id>"\` only for a registered standalone store) and continue until every requested artifact is \`done\`, \`skipped\`, or was deliberately skipped because its own \`instruction\` stated a condition that did not apply. Tell the user about a deliberate conditional skip, remember it, and do not reconsider it. Dependencies are enablers, not gates: if a requested artifact is still \`blocked\` only because you deliberately skipped a conditional prerequisite, run \`openspec instructions "<artifact-id>" --change "<name>" --json\` (append the confirmed \`--store "<id>"\` only for a registered standalone store) despite the blocked status, then create it using step 3 only when those recorded conditional skips are its sole missing dependencies. If a requested artifact is blocked by a prerequisite the user did not ask to capture and cannot be conditionally skipped, explain that dependency and ask before expanding the capture.
|
|
496
496
|
|
|
497
497
|
Capture the artifact(s) the user requested without asking them to invoke another workflow command. If they asked only to start a change, stop after scaffolding and show its status. When the requested capture is done, stop there and name where the work continues: ${CAPTURE_PLANNING_HANDOFF}, and ${CAPTURE_APPLY_HANDOFF}. Capturing artifacts never starts implementing them.
|
|
@@ -73,8 +73,9 @@ ${PROJECT_ROOT_GUARD}
|
|
|
73
73
|
- Identify the target project from the request and project context; the planning home may be separate from the code. If the target is unclear, ask. For greenfield or non-code changes, inspect the available structure and relevant documents. If source is unavailable, state the limitation and ask when it materially affects the plan.
|
|
74
74
|
- Ground scope, approach, and tasks in what you find. Distinguish observed behavior from assumptions and proposed additions; surface conflicts with existing specs instead of silently deciding which is correct.
|
|
75
75
|
- Do this discovery now, rather than leaving generic "explore the codebase" or "make a plan" tasks for implementation. Keep any necessary follow-up investigation specific to an unresolved question.
|
|
76
|
-
-
|
|
77
|
-
-
|
|
76
|
+
- Choose the output path before creating anything. If \`resolvedOutputPath\` is a glob, follow \`instruction\` to choose a concrete path inside \`changeRoot\` that matches the glob, and verify it remains inside \`changeRoot\` after resolving symlinks, both in its parent directories and in the file itself if it already exists; if it does not, stop and tell the user. Otherwise the output path is \`resolvedOutputPath\`
|
|
77
|
+
- If the \`instruction\` field delegates creation to a specific skill or command, invoke it only if it can write the artifact to that output path; otherwise stop and tell the user. Then verify the artifact file exists at that output path
|
|
78
|
+
- Otherwise create the artifact file using \`template\` as the structure and write it to that output path
|
|
78
79
|
- Apply \`context\` and \`rules\` as constraints - but do NOT copy them into the file
|
|
79
80
|
- Show brief progress: "✓ Created <artifact-id>"
|
|
80
81
|
|
|
@@ -108,7 +109,7 @@ After completing all artifacts, summarize:
|
|
|
108
109
|
**Artifact Creation Guidelines**
|
|
109
110
|
|
|
110
111
|
- Follow the \`instruction\` field from \`openspec instructions\` for each artifact type - it is the authoritative guidance, even for familiar artifact names
|
|
111
|
-
- If the \`instruction\` field directs you to use a specific skill or command to create the artifact, invoke it instead of writing the artifact directly
|
|
112
|
+
- If the \`instruction\` field directs you to use a specific skill or command to create the artifact, invoke it instead of writing the artifact directly - but only once you have chosen and checked the output path, and only if it can write there
|
|
112
113
|
- The schema defines what each artifact should contain - follow it
|
|
113
114
|
- Read dependency artifacts for context before creating new ones
|
|
114
115
|
- Use \`template\` as the structure for your output file - fill in its sections
|
|
@@ -191,8 +192,9 @@ ${PROJECT_ROOT_GUARD}
|
|
|
191
192
|
- Identify the target project from the request and project context; the planning home may be separate from the code. If the target is unclear, ask. For greenfield or non-code changes, inspect the available structure and relevant documents. If source is unavailable, state the limitation and ask when it materially affects the plan.
|
|
192
193
|
- Ground scope, approach, and tasks in what you find. Distinguish observed behavior from assumptions and proposed additions; surface conflicts with existing specs instead of silently deciding which is correct.
|
|
193
194
|
- Do this discovery now, rather than leaving generic "explore the codebase" or "make a plan" tasks for implementation. Keep any necessary follow-up investigation specific to an unresolved question.
|
|
194
|
-
-
|
|
195
|
-
-
|
|
195
|
+
- Choose the output path before creating anything. If \`resolvedOutputPath\` is a glob, follow \`instruction\` to choose a concrete path inside \`changeRoot\` that matches the glob, and verify it remains inside \`changeRoot\` after resolving symlinks, both in its parent directories and in the file itself if it already exists; if it does not, stop and tell the user. Otherwise the output path is \`resolvedOutputPath\`
|
|
196
|
+
- If the \`instruction\` field delegates creation to a specific skill or command, invoke it only if it can write the artifact to that output path; otherwise stop and tell the user. Then verify the artifact file exists at that output path
|
|
197
|
+
- Otherwise create the artifact file using \`template\` as the structure and write it to that output path
|
|
196
198
|
- Apply \`context\` and \`rules\` as constraints - but do NOT copy them into the file
|
|
197
199
|
- Show brief progress: "✓ Created <artifact-id>"
|
|
198
200
|
|
|
@@ -226,7 +228,7 @@ After completing all artifacts, summarize:
|
|
|
226
228
|
**Artifact Creation Guidelines**
|
|
227
229
|
|
|
228
230
|
- Follow the \`instruction\` field from \`openspec instructions\` for each artifact type - it is the authoritative guidance, even for familiar artifact names
|
|
229
|
-
- If the \`instruction\` field directs you to use a specific skill or command to create the artifact, invoke it instead of writing the artifact directly
|
|
231
|
+
- If the \`instruction\` field directs you to use a specific skill or command to create the artifact, invoke it instead of writing the artifact directly - but only once you have chosen and checked the output path, and only if it can write there
|
|
230
232
|
- The schema defines what each artifact should contain - follow it
|
|
231
233
|
- Read dependency artifacts for context before creating new ones
|
|
232
234
|
- Use \`template\` as the structure for your output file - fill in its sections
|
|
@@ -119,8 +119,9 @@ ${PROJECT_ROOT_GUARD}
|
|
|
119
119
|
- Identify the target project from the request and project context; the planning home may be separate from the code. If the target is unclear, ask. For greenfield or non-code changes, inspect the available structure and relevant documents. If source is unavailable, state the limitation and ask when it materially affects the plan.
|
|
120
120
|
- Ground scope, approach, and tasks in what you find. Distinguish observed behavior from assumptions and proposed additions; surface conflicts with existing specs instead of silently deciding which is correct.
|
|
121
121
|
- Do this discovery now, rather than leaving generic "explore the codebase" or "make a plan" tasks for implementation. Keep any necessary follow-up investigation specific to an unresolved question.
|
|
122
|
-
-
|
|
123
|
-
-
|
|
122
|
+
- Choose the output path before creating anything. If \`resolvedOutputPath\` is a glob, follow \`instruction\` to choose a concrete path inside \`changeRoot\` that matches the glob, and verify it remains inside \`changeRoot\` after resolving symlinks, both in its parent directories and in the file itself if it already exists; if it does not, stop and tell the user. Otherwise the output path is \`resolvedOutputPath\`
|
|
123
|
+
- If the \`instruction\` field delegates creation to a specific skill or command, invoke it only if it can write the artifact to that output path; otherwise stop and tell the user. Then verify the artifact file exists at that output path
|
|
124
|
+
- Otherwise create the artifact file using \`template\` as the structure and write it to that output path
|
|
124
125
|
- Apply \`context\` and \`rules\` as constraints - but do NOT copy them into the file
|
|
125
126
|
- Show brief progress: "Created <artifact-id>"
|
|
126
127
|
|
|
@@ -154,7 +155,7 @@ After completing all artifacts, summarize:
|
|
|
154
155
|
**Artifact Creation Guidelines**
|
|
155
156
|
|
|
156
157
|
- Follow the \`instruction\` field from \`openspec instructions\` for each artifact type - it is the authoritative guidance, even for familiar artifact names
|
|
157
|
-
- If the \`instruction\` field directs you to use a specific skill or command to create the artifact, invoke it instead of writing the artifact directly
|
|
158
|
+
- If the \`instruction\` field directs you to use a specific skill or command to create the artifact, invoke it instead of writing the artifact directly - but only once you have chosen and checked the output path, and only if it can write there
|
|
158
159
|
- The schema defines what each artifact should contain - follow it
|
|
159
160
|
- Read dependency artifacts for context before creating new ones
|
|
160
161
|
- Use \`template\` as the structure for your output file - fill in its sections
|
|
@@ -283,8 +284,9 @@ ${PROJECT_ROOT_GUARD}
|
|
|
283
284
|
- Identify the target project from the request and project context; the planning home may be separate from the code. If the target is unclear, ask. For greenfield or non-code changes, inspect the available structure and relevant documents. If source is unavailable, state the limitation and ask when it materially affects the plan.
|
|
284
285
|
- Ground scope, approach, and tasks in what you find. Distinguish observed behavior from assumptions and proposed additions; surface conflicts with existing specs instead of silently deciding which is correct.
|
|
285
286
|
- Do this discovery now, rather than leaving generic "explore the codebase" or "make a plan" tasks for implementation. Keep any necessary follow-up investigation specific to an unresolved question.
|
|
286
|
-
-
|
|
287
|
-
-
|
|
287
|
+
- Choose the output path before creating anything. If \`resolvedOutputPath\` is a glob, follow \`instruction\` to choose a concrete path inside \`changeRoot\` that matches the glob, and verify it remains inside \`changeRoot\` after resolving symlinks, both in its parent directories and in the file itself if it already exists; if it does not, stop and tell the user. Otherwise the output path is \`resolvedOutputPath\`
|
|
288
|
+
- If the \`instruction\` field delegates creation to a specific skill or command, invoke it only if it can write the artifact to that output path; otherwise stop and tell the user. Then verify the artifact file exists at that output path
|
|
289
|
+
- Otherwise create the artifact file using \`template\` as the structure and write it to that output path
|
|
288
290
|
- Apply \`context\` and \`rules\` as constraints - but do NOT copy them into the file
|
|
289
291
|
- Show brief progress: "Created <artifact-id>"
|
|
290
292
|
|
|
@@ -318,7 +320,7 @@ After completing all artifacts, summarize:
|
|
|
318
320
|
**Artifact Creation Guidelines**
|
|
319
321
|
|
|
320
322
|
- Follow the \`instruction\` field from \`openspec instructions\` for each artifact type - it is the authoritative guidance, even for familiar artifact names
|
|
321
|
-
- If the \`instruction\` field directs you to use a specific skill or command to create the artifact, invoke it instead of writing the artifact directly
|
|
323
|
+
- If the \`instruction\` field directs you to use a specific skill or command to create the artifact, invoke it instead of writing the artifact directly - but only once you have chosen and checked the output path, and only if it can write there
|
|
322
324
|
- The schema defines what each artifact should contain - follow it
|
|
323
325
|
- Read dependency artifacts for context before creating new ones
|
|
324
326
|
- Use \`template\` as the structure for your output file - fill in its sections
|
|
@@ -87,8 +87,8 @@ ${PROJECT_ROOT_GUARD}
|
|
|
87
87
|
- Add CRITICAL issue: "Requirement not found: <requirement name>"
|
|
88
88
|
- Recommendation: "Implement requirement X: <description>"
|
|
89
89
|
- For each REMOVED requirement, the change asks for the behavior to be gone, so invert the check:
|
|
90
|
-
- A REMOVED block carries only the requirement name and any \`**Reason**\`/\`**Migration**\` notes, so the delta does not describe the behavior itself. Before searching, read the baseline requirement in the main spec at \`<planningHome.root>/openspec/specs/<capability-path>/spec.md\`, using the same capability path as the delta spec. Match it by normalized name,
|
|
91
|
-
- If that file holds no
|
|
90
|
+
- A REMOVED block carries only the requirement name and any \`**Reason**\`/\`**Migration**\` notes, so the delta does not describe the behavior itself. Before searching, read the baseline requirement in the main spec at \`<planningHome.root>/openspec/specs/<capability-path>/spec.md\`, using the same capability path as the delta spec. Match it by exact normalized name, the rule OpenSpec uses when it applies REMOVED blocks: the heading text after \`Requirement:\`, trimmed, with a closing \`#\` run dropped only when a space or tab precedes it (so \`C#\` keeps its \`#\`), compared case-sensitively. A similarly worded heading is not a match; a rename belongs under RENAMED. Its body and scenarios are the evidence for the behavior that must be gone; never search from the requirement name alone.
|
|
91
|
+
- If that file holds no requirement with that exact normalized name - for example because an earlier sync already applied the removal, or a similarly named ADDED requirement replaced it - recover the baseline from the same file's history, in the planning repository rather than the current directory: \`git -C "<planningHome.root>" log -p -- openspec/specs/<capability-path>/spec.md\`, or \`git -C "<planningHome.root>" show <commit>^:./openspec/specs/<capability-path>/spec.md\` once you have identified the commit that applied the removal. Empty output means the pathspec matched nothing, not that there is no baseline: \`git log\` reports an unmatched path as success, so re-check the path before drawing any conclusion from silence.
|
|
92
92
|
- A baseline that adds nothing to its own title - no body beyond a restatement of the name, no scenarios - is unusable evidence, the same as a missing one. If no usable baseline is available in either place, mark **Spec Coverage** as not verified for that entry with the reason and stop there: do not search, and never count an unsearched removal as passing.
|
|
93
93
|
- Search codebase for that behavior. Matches in \`openspec/\` artifacts or docs, or in code that serves only the Migration note or an ADDED requirement, are not evidence by themselves. Report any code path that still delivers the removed behavior, including one shared with an ADDED requirement.
|
|
94
94
|
- Finding no implementation is the expected result, but only once the baseline behavior was the thing you searched for. Never report a REMOVED requirement as "Requirement not found" or recommend implementing it.
|
|
@@ -299,8 +299,8 @@ ${PROJECT_ROOT_GUARD}
|
|
|
299
299
|
- Add CRITICAL issue: "Requirement not found: <requirement name>"
|
|
300
300
|
- Recommendation: "Implement requirement X: <description>"
|
|
301
301
|
- For each REMOVED requirement, the change asks for the behavior to be gone, so invert the check:
|
|
302
|
-
- A REMOVED block carries only the requirement name and any \`**Reason**\`/\`**Migration**\` notes, so the delta does not describe the behavior itself. Before searching, read the baseline requirement in the main spec at \`<planningHome.root>/openspec/specs/<capability-path>/spec.md\`, using the same capability path as the delta spec. Match it by normalized name,
|
|
303
|
-
- If that file holds no
|
|
302
|
+
- A REMOVED block carries only the requirement name and any \`**Reason**\`/\`**Migration**\` notes, so the delta does not describe the behavior itself. Before searching, read the baseline requirement in the main spec at \`<planningHome.root>/openspec/specs/<capability-path>/spec.md\`, using the same capability path as the delta spec. Match it by exact normalized name, the rule OpenSpec uses when it applies REMOVED blocks: the heading text after \`Requirement:\`, trimmed, with a closing \`#\` run dropped only when a space or tab precedes it (so \`C#\` keeps its \`#\`), compared case-sensitively. A similarly worded heading is not a match; a rename belongs under RENAMED. Its body and scenarios are the evidence for the behavior that must be gone; never search from the requirement name alone.
|
|
303
|
+
- If that file holds no requirement with that exact normalized name - for example because an earlier sync already applied the removal, or a similarly named ADDED requirement replaced it - recover the baseline from the same file's history, in the planning repository rather than the current directory: \`git -C "<planningHome.root>" log -p -- openspec/specs/<capability-path>/spec.md\`, or \`git -C "<planningHome.root>" show <commit>^:./openspec/specs/<capability-path>/spec.md\` once you have identified the commit that applied the removal. Empty output means the pathspec matched nothing, not that there is no baseline: \`git log\` reports an unmatched path as success, so re-check the path before drawing any conclusion from silence.
|
|
304
304
|
- A baseline that adds nothing to its own title - no body beyond a restatement of the name, no scenarios - is unusable evidence, the same as a missing one. If no usable baseline is available in either place, mark **Spec Coverage** as not verified for that entry with the reason and stop there: do not search, and never count an unsearched removal as passing.
|
|
305
305
|
- Search codebase for that behavior. Matches in \`openspec/\` artifacts or docs, or in code that serves only the Migration note or an ADDED requirement, are not evidence by themselves. Report any code path that still delivers the removed behavior, including one shared with an ADDED requirement.
|
|
306
306
|
- Finding no implementation is the expected result, but only once the baseline behavior was the thing you searched for. Never report a REMOVED requirement as "Requirement not found" or recommend implementing it.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update Plan
|
|
3
|
+
*
|
|
4
|
+
* The write seam `update` uses for every file it generates or removes.
|
|
5
|
+
*
|
|
6
|
+
* A real run forwards to the filesystem. A dry run records what the same call
|
|
7
|
+
* would have done and writes nothing, so the preview and the real update come
|
|
8
|
+
* from one code path instead of two descriptions of it.
|
|
9
|
+
*/
|
|
10
|
+
import type { FileView } from './file-view.js';
|
|
11
|
+
export type UpdatePlanAction = 'add' | 'update' | 'delete';
|
|
12
|
+
export interface UpdatePlanChange {
|
|
13
|
+
/** Project-relative POSIX path, or an absolute path (home collapsed to `~`) for files outside the project. */
|
|
14
|
+
path: string;
|
|
15
|
+
action: UpdatePlanAction;
|
|
16
|
+
/**
|
|
17
|
+
* Tool the file belongs to, as configured (e.g. `claude`), or `openspec`
|
|
18
|
+
* for OpenSpec's own files: the global config and legacy-artifact cleanup.
|
|
19
|
+
*/
|
|
20
|
+
tool: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* What the plan cannot settle by itself: a step a terminal user is asked
|
|
24
|
+
* about, which the plan lists as a non-interactive run makes it. Stable
|
|
25
|
+
* identifiers, for a caller that branches on them.
|
|
26
|
+
*/
|
|
27
|
+
export type UpdatePlanNoteCode = 'legacy-tool-directories' | 'legacy-artifacts';
|
|
28
|
+
export interface UpdatePlanNote {
|
|
29
|
+
code: UpdatePlanNoteCode;
|
|
30
|
+
message: string;
|
|
31
|
+
}
|
|
32
|
+
export interface UpdatePlanFailure {
|
|
33
|
+
tool: string;
|
|
34
|
+
error: string;
|
|
35
|
+
}
|
|
36
|
+
export interface UpdatePlan {
|
|
37
|
+
changes: UpdatePlanChange[];
|
|
38
|
+
/**
|
|
39
|
+
* Steps a terminal user would be asked about. `changes` lists them as a
|
|
40
|
+
* non-interactive run makes them. Empty for an ordinary refresh.
|
|
41
|
+
*/
|
|
42
|
+
notes: UpdatePlanNote[];
|
|
43
|
+
/**
|
|
44
|
+
* Tools the real update would fail on. Reported rather than thrown: one
|
|
45
|
+
* broken tool must not cost the caller the rest of the plan.
|
|
46
|
+
*/
|
|
47
|
+
failures: UpdatePlanFailure[];
|
|
48
|
+
}
|
|
49
|
+
/** Every filesystem mutation `update` makes while generating artifacts. */
|
|
50
|
+
export interface UpdateFileOps {
|
|
51
|
+
writeFile(filePath: string, content: string, tool: string): Promise<void>;
|
|
52
|
+
removeFile(filePath: string, tool: string): Promise<void>;
|
|
53
|
+
removeDirectory(dirPath: string, tool: string): Promise<void>;
|
|
54
|
+
/** Removes a directory already known to be empty. */
|
|
55
|
+
removeEmptyDirectory(dirPath: string): Promise<void>;
|
|
56
|
+
}
|
|
57
|
+
export declare const realFileOps: UpdateFileOps;
|
|
58
|
+
export declare class UpdatePlanRecorder implements UpdateFileOps {
|
|
59
|
+
private readonly projectPath;
|
|
60
|
+
private readonly notes;
|
|
61
|
+
private readonly failures;
|
|
62
|
+
/**
|
|
63
|
+
* The tree as the real update would have it so far: absolute path to planned
|
|
64
|
+
* content, a moved directory, or null once planned away (a removed directory
|
|
65
|
+
* takes everything under it). Steps that read what an earlier step left
|
|
66
|
+
* behind read this through `view`.
|
|
67
|
+
*/
|
|
68
|
+
private readonly staged;
|
|
69
|
+
/** Every file path the plan has touched, with the tool that touched it last. */
|
|
70
|
+
private readonly touched;
|
|
71
|
+
constructor(projectPath: string);
|
|
72
|
+
writeFile(filePath: string, content: string, tool: string): Promise<void>;
|
|
73
|
+
removeFile(filePath: string, tool: string): Promise<void>;
|
|
74
|
+
removeEmptyDirectory(dirPath: string): Promise<void>;
|
|
75
|
+
removeDirectory(dirPath: string, tool: string): Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* A rename, as the legacy-root migration makes it: whatever the entry is
|
|
78
|
+
* moves, a directory with everything in it.
|
|
79
|
+
*/
|
|
80
|
+
moveFile(source: string, destination: string, tool: string): Promise<void>;
|
|
81
|
+
/**
|
|
82
|
+
* The tree with every planned write, move and removal applied. A path the
|
|
83
|
+
* plan has not touched answers exactly as the disk does.
|
|
84
|
+
*/
|
|
85
|
+
readonly view: FileView;
|
|
86
|
+
addNote(code: UpdatePlanNoteCode, message: string): void;
|
|
87
|
+
addFailure(tool: string, error: string): void;
|
|
88
|
+
/**
|
|
89
|
+
* Each touched path, compared as it is on disk now with how the plan leaves
|
|
90
|
+
* it. A file written and then removed, or rewritten with the bytes it
|
|
91
|
+
* already had, is no change at all.
|
|
92
|
+
*/
|
|
93
|
+
getPlan(): UpdatePlan;
|
|
94
|
+
private stage;
|
|
95
|
+
/**
|
|
96
|
+
* What the plan has put at a path: content, a moved directory, null once
|
|
97
|
+
* planned away, or undefined when the plan has not touched it.
|
|
98
|
+
*/
|
|
99
|
+
private stagedEntry;
|
|
100
|
+
/** Whether the plan has put any file or directory below `directory`. */
|
|
101
|
+
private hasStagedDescendant;
|
|
102
|
+
/** Files under a directory in the tree as planned: those on disk, and those the plan added. */
|
|
103
|
+
private filesUnder;
|
|
104
|
+
/**
|
|
105
|
+
* What is at a path in the tree as planned, and whether that is what is on
|
|
106
|
+
* disk (so its permissions are real).
|
|
107
|
+
*/
|
|
108
|
+
private entryKind;
|
|
109
|
+
/**
|
|
110
|
+
* Fails the way the real write would, without writing: a directory where the
|
|
111
|
+
* file belongs, a file where one of its directories belongs, or no
|
|
112
|
+
* permission. Recording the write instead would plan past the point where
|
|
113
|
+
* the real update stops for this tool, listing files it never writes.
|
|
114
|
+
*/
|
|
115
|
+
private probeWrite;
|
|
116
|
+
/** Fails the way unlinking the file would. */
|
|
117
|
+
private probeRemove;
|
|
118
|
+
/**
|
|
119
|
+
* The nearest existing ancestor has to be a directory the update can create
|
|
120
|
+
* entries in; anything it would create below that starts out empty.
|
|
121
|
+
*/
|
|
122
|
+
private probeCreatableIn;
|
|
123
|
+
private toDisplayPath;
|
|
124
|
+
}
|
|
125
|
+
/** Human-readable rendering of a dry-run plan. */
|
|
126
|
+
export declare function formatUpdatePlan(plan: UpdatePlan): string;
|
|
127
|
+
//# sourceMappingURL=update-plan.d.ts.map
|