@niujiajie/freespec 0.0.0-stage → 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/LICENSE +22 -0
- package/README.md +58 -2
- package/bin/freespec.js +5 -0
- package/dist/cli/index.d.ts +25 -0
- package/dist/cli/index.js +359 -0
- package/dist/commands/shared-output.d.ts +18 -0
- package/dist/commands/shared-output.js +61 -0
- package/dist/commands/workflow/default-schema.d.ts +2 -0
- package/dist/commands/workflow/default-schema.js +4 -0
- package/dist/commands/workflow/index.d.ts +13 -0
- package/dist/commands/workflow/index.js +10 -0
- package/dist/commands/workflow/instructions.d.ts +39 -0
- package/dist/commands/workflow/instructions.js +565 -0
- package/dist/commands/workflow/new-change.d.ts +19 -0
- package/dist/commands/workflow/new-change.js +135 -0
- package/dist/commands/workflow/shared.d.ts +94 -0
- package/dist/commands/workflow/shared.js +169 -0
- package/dist/commands/workflow/status.d.ts +18 -0
- package/dist/commands/workflow/status.js +201 -0
- package/dist/core/artifact-graph/graph.d.ts +72 -0
- package/dist/core/artifact-graph/graph.js +165 -0
- package/dist/core/artifact-graph/index.d.ts +9 -0
- package/dist/core/artifact-graph/index.js +14 -0
- package/dist/core/artifact-graph/instruction-loader.d.ts +208 -0
- package/dist/core/artifact-graph/instruction-loader.js +268 -0
- package/dist/core/artifact-graph/outputs.d.ts +19 -0
- package/dist/core/artifact-graph/outputs.js +351 -0
- package/dist/core/artifact-graph/resolver.d.ts +89 -0
- package/dist/core/artifact-graph/resolver.js +342 -0
- package/dist/core/artifact-graph/schema.d.ts +27 -0
- package/dist/core/artifact-graph/schema.js +160 -0
- package/dist/core/artifact-graph/state.d.ts +12 -0
- package/dist/core/artifact-graph/state.js +31 -0
- package/dist/core/artifact-graph/types.d.ts +40 -0
- package/dist/core/artifact-graph/types.js +59 -0
- package/dist/core/available-tools.d.ts +18 -0
- package/dist/core/available-tools.js +49 -0
- package/dist/core/change-layout.d.ts +17 -0
- package/dist/core/change-layout.js +30 -0
- package/dist/core/change-metadata/index.d.ts +2 -0
- package/dist/core/change-metadata/index.js +2 -0
- package/dist/core/change-metadata/schema.d.ts +23 -0
- package/dist/core/change-metadata/schema.js +46 -0
- package/dist/core/change-status-policy.d.ts +49 -0
- package/dist/core/change-status-policy.js +50 -0
- package/dist/core/command-generation/adapters/amazon-q.d.ts +17 -0
- package/dist/core/command-generation/adapters/amazon-q.js +32 -0
- package/dist/core/command-generation/adapters/antigravity.d.ts +13 -0
- package/dist/core/command-generation/adapters/antigravity.js +27 -0
- package/dist/core/command-generation/adapters/atomcode.d.ts +24 -0
- package/dist/core/command-generation/adapters/atomcode.js +46 -0
- package/dist/core/command-generation/adapters/auggie.d.ts +13 -0
- package/dist/core/command-generation/adapters/auggie.js +28 -0
- package/dist/core/command-generation/adapters/bob.d.ts +18 -0
- package/dist/core/command-generation/adapters/bob.js +33 -0
- package/dist/core/command-generation/adapters/claude.d.ts +13 -0
- package/dist/core/command-generation/adapters/claude.js +32 -0
- package/dist/core/command-generation/adapters/cline.d.ts +14 -0
- package/dist/core/command-generation/adapters/cline.js +27 -0
- package/dist/core/command-generation/adapters/codeassistant.d.ts +15 -0
- package/dist/core/command-generation/adapters/codeassistant.js +29 -0
- package/dist/core/command-generation/adapters/codebuddy.d.ts +13 -0
- package/dist/core/command-generation/adapters/codebuddy.js +29 -0
- package/dist/core/command-generation/adapters/codestudio.d.ts +13 -0
- package/dist/core/command-generation/adapters/codestudio.js +27 -0
- package/dist/core/command-generation/adapters/command-code.d.ts +19 -0
- package/dist/core/command-generation/adapters/command-code.js +34 -0
- package/dist/core/command-generation/adapters/continue.d.ts +13 -0
- package/dist/core/command-generation/adapters/continue.js +33 -0
- package/dist/core/command-generation/adapters/costrict.d.ts +13 -0
- package/dist/core/command-generation/adapters/costrict.js +28 -0
- package/dist/core/command-generation/adapters/crush.d.ts +13 -0
- package/dist/core/command-generation/adapters/crush.js +30 -0
- package/dist/core/command-generation/adapters/cursor.d.ts +14 -0
- package/dist/core/command-generation/adapters/cursor.js +31 -0
- package/dist/core/command-generation/adapters/devin.d.ts +19 -0
- package/dist/core/command-generation/adapters/devin.js +36 -0
- package/dist/core/command-generation/adapters/easycode.d.ts +17 -0
- package/dist/core/command-generation/adapters/easycode.js +28 -0
- package/dist/core/command-generation/adapters/factory.d.ts +13 -0
- package/dist/core/command-generation/adapters/factory.js +28 -0
- package/dist/core/command-generation/adapters/gemini.d.ts +13 -0
- package/dist/core/command-generation/adapters/gemini.js +27 -0
- package/dist/core/command-generation/adapters/gigacode.d.ts +17 -0
- package/dist/core/command-generation/adapters/gigacode.js +31 -0
- package/dist/core/command-generation/adapters/github-copilot.d.ts +13 -0
- package/dist/core/command-generation/adapters/github-copilot.js +27 -0
- package/dist/core/command-generation/adapters/iflow.d.ts +13 -0
- package/dist/core/command-generation/adapters/iflow.js +30 -0
- package/dist/core/command-generation/adapters/index.d.ts +40 -0
- package/dist/core/command-generation/adapters/index.js +40 -0
- package/dist/core/command-generation/adapters/junie.d.ts +13 -0
- package/dist/core/command-generation/adapters/junie.js +27 -0
- package/dist/core/command-generation/adapters/kilocode.d.ts +14 -0
- package/dist/core/command-generation/adapters/kilocode.js +23 -0
- package/dist/core/command-generation/adapters/kiro.d.ts +13 -0
- package/dist/core/command-generation/adapters/kiro.js +27 -0
- package/dist/core/command-generation/adapters/lingma.d.ts +13 -0
- package/dist/core/command-generation/adapters/lingma.js +30 -0
- package/dist/core/command-generation/adapters/oh-my-pi.d.ts +21 -0
- package/dist/core/command-generation/adapters/oh-my-pi.js +42 -0
- package/dist/core/command-generation/adapters/opencode.d.ts +14 -0
- package/dist/core/command-generation/adapters/opencode.js +38 -0
- package/dist/core/command-generation/adapters/pi.d.ts +18 -0
- package/dist/core/command-generation/adapters/pi.js +39 -0
- package/dist/core/command-generation/adapters/qoder.d.ts +13 -0
- package/dist/core/command-generation/adapters/qoder.js +30 -0
- package/dist/core/command-generation/adapters/qwen.d.ts +17 -0
- package/dist/core/command-generation/adapters/qwen.js +31 -0
- package/dist/core/command-generation/adapters/roocode.d.ts +14 -0
- package/dist/core/command-generation/adapters/roocode.js +27 -0
- package/dist/core/command-generation/adapters/trae.d.ts +13 -0
- package/dist/core/command-generation/adapters/trae.js +28 -0
- package/dist/core/command-generation/adapters/zcode.d.ts +16 -0
- package/dist/core/command-generation/adapters/zcode.js +33 -0
- package/dist/core/command-generation/generator.d.ts +29 -0
- package/dist/core/command-generation/generator.js +43 -0
- package/dist/core/command-generation/index.d.ts +22 -0
- package/dist/core/command-generation/index.js +24 -0
- package/dist/core/command-generation/invocation.d.ts +73 -0
- package/dist/core/command-generation/invocation.js +77 -0
- package/dist/core/command-generation/registry.d.ts +36 -0
- package/dist/core/command-generation/registry.js +114 -0
- package/dist/core/command-generation/toml.d.ts +19 -0
- package/dist/core/command-generation/toml.js +39 -0
- package/dist/core/command-generation/types.d.ts +63 -0
- package/dist/core/command-generation/types.js +8 -0
- package/dist/core/command-generation/yaml.d.ts +36 -0
- package/dist/core/command-generation/yaml.js +48 -0
- package/dist/core/command-surface.d.ts +15 -0
- package/dist/core/command-surface.js +33 -0
- package/dist/core/config-prompts.d.ts +9 -0
- package/dist/core/config-prompts.js +56 -0
- package/dist/core/config-schema.d.ts +98 -0
- package/dist/core/config-schema.js +308 -0
- package/dist/core/config.d.ts +50 -0
- package/dist/core/config.js +122 -0
- package/dist/core/file-state.d.ts +36 -0
- package/dist/core/file-state.js +144 -0
- package/dist/core/file-view.d.ts +13 -0
- package/dist/core/file-view.js +14 -0
- package/dist/core/global-config.d.ts +101 -0
- package/dist/core/global-config.js +182 -0
- package/dist/core/id.d.ts +17 -0
- package/dist/core/id.js +30 -0
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.js +7 -0
- package/dist/core/init.d.ts +57 -0
- package/dist/core/init.js +836 -0
- package/dist/core/list.d.ts +11 -0
- package/dist/core/list.js +197 -0
- package/dist/core/migration.d.ts +121 -0
- package/dist/core/migration.js +540 -0
- package/dist/core/onboarding-commands.d.ts +30 -0
- package/dist/core/onboarding-commands.js +38 -0
- package/dist/core/openspec-root.d.ts +47 -0
- package/dist/core/openspec-root.js +196 -0
- package/dist/core/planning-home.d.ts +16 -0
- package/dist/core/planning-home.js +67 -0
- package/dist/core/profile-sync-drift.d.ts +31 -0
- package/dist/core/profile-sync-drift.js +201 -0
- package/dist/core/profiles.d.ts +19 -0
- package/dist/core/profiles.js +33 -0
- package/dist/core/project-config.d.ts +132 -0
- package/dist/core/project-config.js +519 -0
- package/dist/core/references.d.ts +10 -0
- package/dist/core/references.js +46 -0
- package/dist/core/root-selection.d.ts +59 -0
- package/dist/core/root-selection.js +132 -0
- package/dist/core/shared/allowed-tools.d.ts +12 -0
- package/dist/core/shared/allowed-tools.js +12 -0
- package/dist/core/shared/ide-restart.d.ts +26 -0
- package/dist/core/shared/ide-restart.js +42 -0
- package/dist/core/shared/index.d.ts +11 -0
- package/dist/core/shared/index.js +11 -0
- package/dist/core/shared/skill-content-equivalence.d.ts +7 -0
- package/dist/core/shared/skill-content-equivalence.js +45 -0
- package/dist/core/shared/skill-files.d.ts +7 -0
- package/dist/core/shared/skill-files.js +43 -0
- package/dist/core/shared/skill-generation.d.ts +49 -0
- package/dist/core/shared/skill-generation.js +125 -0
- package/dist/core/shared/skill-paths.d.ts +13 -0
- package/dist/core/shared/skill-paths.js +23 -0
- package/dist/core/shared/tool-detection.d.ts +99 -0
- package/dist/core/shared/tool-detection.js +318 -0
- package/dist/core/shared-skill-target.d.ts +51 -0
- package/dist/core/shared-skill-target.js +239 -0
- package/dist/core/store/errors.d.ts +20 -0
- package/dist/core/store/errors.js +22 -0
- package/dist/core/store/index.d.ts +2 -0
- package/dist/core/store/index.js +2 -0
- package/dist/core/styles/palette.d.ts +7 -0
- package/dist/core/styles/palette.js +8 -0
- package/dist/core/templates/optional-workflow.d.ts +69 -0
- package/dist/core/templates/optional-workflow.js +140 -0
- package/dist/core/templates/types.d.ts +19 -0
- package/dist/core/templates/types.js +5 -0
- package/dist/core/templates/workflows/personal.d.ts +22 -0
- package/dist/core/templates/workflows/personal.js +225 -0
- package/dist/core/update-plan.d.ts +127 -0
- package/dist/core/update-plan.js +390 -0
- package/dist/core/update.d.ts +154 -0
- package/dist/core/update.js +723 -0
- package/dist/core/version-check.d.ts +188 -0
- package/dist/core/version-check.js +906 -0
- package/dist/core/zod-issues.d.ts +4 -0
- package/dist/core/zod-issues.js +10 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/prompts/searchable-multi-select.d.ts +35 -0
- package/dist/prompts/searchable-multi-select.js +188 -0
- package/dist/telemetry/config.d.ts +37 -0
- package/dist/telemetry/config.js +148 -0
- package/dist/telemetry/index.d.ts +39 -0
- package/dist/telemetry/index.js +217 -0
- package/dist/telemetry/opt-out.d.ts +17 -0
- package/dist/telemetry/opt-out.js +27 -0
- package/dist/ui/ascii-patterns.d.ts +16 -0
- package/dist/ui/ascii-patterns.js +133 -0
- package/dist/ui/welcome-screen.d.ts +18 -0
- package/dist/ui/welcome-screen.js +199 -0
- package/dist/utils/change-metadata.d.ts +116 -0
- package/dist/utils/change-metadata.js +324 -0
- package/dist/utils/change-utils.d.ts +79 -0
- package/dist/utils/change-utils.js +121 -0
- package/dist/utils/ci.d.ts +12 -0
- package/dist/utils/ci.js +16 -0
- package/dist/utils/command-references.d.ts +104 -0
- package/dist/utils/command-references.js +190 -0
- package/dist/utils/date.d.ts +8 -0
- package/dist/utils/date.js +13 -0
- package/dist/utils/executable-path.d.ts +5 -0
- package/dist/utils/executable-path.js +44 -0
- package/dist/utils/file-system.d.ts +58 -0
- package/dist/utils/file-system.js +432 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.js +9 -0
- package/dist/utils/interactive.d.ts +85 -0
- package/dist/utils/interactive.js +174 -0
- package/dist/utils/item-discovery.d.ts +15 -0
- package/dist/utils/item-discovery.js +33 -0
- package/dist/utils/line-endings.d.ts +34 -0
- package/dist/utils/line-endings.js +46 -0
- package/dist/utils/match.d.ts +3 -0
- package/dist/utils/match.js +22 -0
- package/dist/utils/nested-change.d.ts +35 -0
- package/dist/utils/nested-change.js +185 -0
- package/dist/utils/shell-detection.d.ts +20 -0
- package/dist/utils/shell-detection.js +94 -0
- package/dist/utils/task-progress.d.ts +67 -0
- package/dist/utils/task-progress.js +184 -0
- package/package.json +74 -4
- package/schemas//351/273/230/350/256/244/schema.yaml +68 -0
- package/schemas//351/273/230/350/256/244/templates//344/273/273/345/212/241/346/270/205/345/215/225.md +25 -0
- package/schemas//351/273/230/350/256/244/templates//346/265/213/350/257/225/347/224/250/344/276/213.md +34 -0
- package/schemas//351/273/230/350/256/244/templates//350/256/276/350/256/241/346/226/207/346/241/243.md +172 -0
- package/schemas//351/273/230/350/256/244/templates//351/234/200/346/261/202/346/226/207/346/241/243.md +19 -0
- package/skill-files//344/273/273/345/212/241/references//346/212/244/346/240/217.md +43 -0
- package/skill-files//344/273/273/345/212/241/references//347/262/222/345/272/246.md +23 -0
- package/skill-files//344/273/273/345/212/241/references//347/272/246/346/235/237.md +8 -0
- package/skill-files//344/273/273/345/212/241/scripts/check-tasks.mjs +83 -0
- package/skill-files//345/206/231Java/references/Java/350/247/204/350/214/203.md +25 -0
- package/skill-files//345/206/231Java/references//345/215/261/351/231/251/345/206/231/346/263/225.md +27 -0
- package/skill-files//345/206/231Java/references//345/256/214/346/210/220/345/257/271/347/205/247.md +15 -0
- package/skill-files//345/206/231Java/references//346/212/244/346/240/217.md +35 -0
- package/skill-files//345/206/231Java/references//347/272/246/346/235/237.md +8 -0
- package/skill-files//345/206/231Java/references//350/270/251/345/235/221.md +17 -0
- package/skill-files//345/256/241Java/references//346/212/244/346/240/217.md +19 -0
- package/skill-files//345/256/241Java/references//347/244/272/344/276/213.md +23 -0
- package/skill-files//345/256/241Java/references//347/272/246/346/235/237.md +7 -0
- package/skill-files//345/256/241Java/references//350/247/204/345/210/231.md +23 -0
- package/skill-files//345/256/241Java/references//351/242/206/345/237/237/346/243/200/351/241/271.md +58 -0
- package/skill-files//345/256/241/346/226/207/346/241/243/references//346/212/244/346/240/217.md +18 -0
- package/skill-files//345/256/241/346/226/207/346/241/243/references//347/272/246/346/235/237.md +8 -0
- package/skill-files//345/256/241/346/226/207/346/241/243/references//347/273/264/345/272/246.md +17 -0
- package/skill-files//345/256/241/346/226/207/346/241/243/references//350/270/251/345/235/221.md +11 -0
- package/skill-files//346/226/260/345/273/272/references//344/272/244/346/216/245.md +11 -0
- package/skill-files//346/226/260/345/273/272/references//346/212/244/346/240/217.md +14 -0
- package/skill-files//346/226/260/345/273/272/references//347/272/246/346/235/237.md +7 -0
- package/skill-files//346/267/261/346/200/235/config/prompts.yaml +114 -0
- package/skill-files//346/267/261/346/200/235/references//344/270/211/346/226/271/346/240/270/345/257/271.md +72 -0
- package/skill-files//346/267/261/346/200/235/references//344/276/213/345/255/220.md +39 -0
- package/skill-files//346/267/261/346/200/235/references//346/200/216/344/271/210/346/203/263.md +61 -0
- package/skill-files//346/267/261/346/200/235/references//346/212/244/346/240/217.md +26 -0
- package/skill-files//346/267/261/346/200/235/references//347/202/271/351/200/211/346/217/220/351/227/256.md +66 -0
- package/skill-files//346/267/261/346/200/235/references//347/231/273/350/256/260.md +45 -0
- package/skill-files//346/267/261/346/200/235/references//347/272/246/346/235/237.md +9 -0
- package/skill-files//347/224/250/344/276/213/references//346/200/216/344/271/210/345/206/231.md +13 -0
- package/skill-files//347/224/250/344/276/213/references//346/212/244/346/240/217.md +42 -0
- package/skill-files//347/224/250/344/276/213/references//347/272/246/346/235/237.md +5 -0
- package/skill-files//347/224/250/344/276/213/scripts/check-cases.mjs +70 -0
- package/skill-files//350/256/276/350/256/241/references//345/207/206/345/244/207.md +25 -0
- package/skill-files//350/256/276/350/256/241/references//346/212/244/346/240/217.md +134 -0
- package/skill-files//350/256/276/350/256/241/references//346/226/207/351/243/216.md +17 -0
- package/skill-files//350/256/276/350/256/241/references//347/225/214/351/235/242.md +18 -0
- package/skill-files//350/256/276/350/256/241/references//347/261/273/345/236/213.md +19 -0
- package/skill-files//350/256/276/350/256/241/references//347/272/246/346/235/237.md +25 -0
- package/skill-files//350/256/276/350/256/241/scripts/_smoke.mjs +72 -0
- package/skill-files//350/256/276/350/256/241/scripts/check-design.mjs +331 -0
- package/skill-files//351/234/200/346/261/202/references//346/212/244/346/240/217.md +26 -0
- package/skill-files//351/234/200/346/261/202/references//346/226/207/351/243/216.md +23 -0
- package/skill-files//351/234/200/346/261/202/references//346/267/261/346/200/235/346/221/204/345/205/245.md +21 -0
- package/skill-files//351/234/200/346/261/202/references//347/272/246/346/235/237.md +8 -0
- package/skill-files//351/234/200/346/261/202/references//351/234/200/346/261/202/345/276/205/345/256/232.md +13 -0
- package/skill-files//351/234/200/346/261/202/scripts/check-requirements.mjs +60 -0
- package/skill-files//351/252/214/346/224/266/references//346/212/244/346/240/217.md +27 -0
- package/skill-files//351/252/214/346/224/266/references//346/240/270/345/257/271.md +17 -0
- package/skill-files//351/252/214/346/224/266/references//347/272/246/346/235/237.md +7 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { FileSystemUtils } from '../utils/file-system.js';
|
|
4
|
+
import { DEFAULT_SCHEMA as DEFAULT_OPENSPEC_SCHEMA } from './change-layout.js';
|
|
5
|
+
import { serializeConfig } from './config-prompts.js';
|
|
6
|
+
import { makeStoreDiagnostic, } from './store/errors.js';
|
|
7
|
+
export const OPENSPEC_ROOT_DIR = 'freespec';
|
|
8
|
+
export const OPENSPEC_CONFIG_YAML = 'freespec/config.yaml';
|
|
9
|
+
export const OPENSPEC_CONFIG_YML = 'freespec/config.yml';
|
|
10
|
+
export const OPENSPEC_SPECS_DIR = 'freespec/specs';
|
|
11
|
+
/** Change folders sit directly under `freespec/`, named `<名称>变更`. */
|
|
12
|
+
export const OPENSPEC_CHANGES_DIR = 'freespec';
|
|
13
|
+
export { DEFAULT_OPENSPEC_SCHEMA };
|
|
14
|
+
export const DIRECTORY_ANCHOR_FILE_NAME = '.gitkeep';
|
|
15
|
+
// Git cannot track empty directories, so setup anchors otherwise-empty
|
|
16
|
+
// conventional store directories for teammates who clone the repo later.
|
|
17
|
+
export const ANCHORED_OPENSPEC_DIRS = [];
|
|
18
|
+
async function pathKind(targetPath) {
|
|
19
|
+
try {
|
|
20
|
+
const stat = await fs.stat(targetPath);
|
|
21
|
+
if (stat.isDirectory())
|
|
22
|
+
return 'directory';
|
|
23
|
+
if (stat.isFile())
|
|
24
|
+
return 'file';
|
|
25
|
+
return 'other';
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
if (typeof error === 'object' &&
|
|
29
|
+
error !== null &&
|
|
30
|
+
'code' in error &&
|
|
31
|
+
error.code === 'ENOENT') {
|
|
32
|
+
return 'missing';
|
|
33
|
+
}
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function relativeArtifact(relativePath, kind) {
|
|
38
|
+
const normalized = FileSystemUtils.toPosixPath(relativePath);
|
|
39
|
+
return kind === 'directory' ? `${normalized}/` : normalized;
|
|
40
|
+
}
|
|
41
|
+
function unresolvedInspection() {
|
|
42
|
+
return {
|
|
43
|
+
present: null,
|
|
44
|
+
config: { present: null },
|
|
45
|
+
specs: { present: null },
|
|
46
|
+
changes: { present: null },
|
|
47
|
+
archive: { present: null },
|
|
48
|
+
healthy: false,
|
|
49
|
+
diagnostics: [],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function missingDirectoryDiagnostic(code, message, target) {
|
|
53
|
+
return makeStoreDiagnostic('error', code, message, { target });
|
|
54
|
+
}
|
|
55
|
+
async function inspectOptionalPlanningDirectory(inspection, storeRoot, key, relativePath, notDirectoryCode, target) {
|
|
56
|
+
const kind = await pathKind(path.join(storeRoot, relativePath));
|
|
57
|
+
inspection[key] = { present: kind === 'directory' };
|
|
58
|
+
if (kind === 'directory' || kind === 'missing')
|
|
59
|
+
return kind;
|
|
60
|
+
inspection.diagnostics.push(missingDirectoryDiagnostic(notDirectoryCode, `${relativePath}/ exists but is not a directory.`, target));
|
|
61
|
+
return kind;
|
|
62
|
+
}
|
|
63
|
+
export async function inspectFreeSpecRoot(storeRoot) {
|
|
64
|
+
const rootKind = await pathKind(storeRoot);
|
|
65
|
+
const inspection = unresolvedInspection();
|
|
66
|
+
if (rootKind === 'missing') {
|
|
67
|
+
inspection.diagnostics.push(missingDirectoryDiagnostic('freespec_store_root_missing', 'Store root does not exist.', 'store.root'));
|
|
68
|
+
return inspection;
|
|
69
|
+
}
|
|
70
|
+
if (rootKind !== 'directory') {
|
|
71
|
+
inspection.diagnostics.push(missingDirectoryDiagnostic('freespec_store_root_not_directory', 'Store root is not a directory.', 'store.root'));
|
|
72
|
+
return inspection;
|
|
73
|
+
}
|
|
74
|
+
const freespecPath = path.join(storeRoot, OPENSPEC_ROOT_DIR);
|
|
75
|
+
const freespecKind = await pathKind(freespecPath);
|
|
76
|
+
inspection.present = freespecKind === 'directory';
|
|
77
|
+
if (freespecKind === 'missing') {
|
|
78
|
+
inspection.diagnostics.push(missingDirectoryDiagnostic('freespec_root_missing', 'Missing freespec/ directory.', 'freespec.root'));
|
|
79
|
+
return inspection;
|
|
80
|
+
}
|
|
81
|
+
if (freespecKind !== 'directory') {
|
|
82
|
+
inspection.diagnostics.push(missingDirectoryDiagnostic('freespec_root_not_directory', 'freespec/ exists but is not a directory.', 'freespec.root'));
|
|
83
|
+
return inspection;
|
|
84
|
+
}
|
|
85
|
+
const configYamlKind = await pathKind(path.join(storeRoot, OPENSPEC_CONFIG_YAML));
|
|
86
|
+
const configYmlKind = await pathKind(path.join(storeRoot, OPENSPEC_CONFIG_YML));
|
|
87
|
+
if (configYamlKind === 'file') {
|
|
88
|
+
inspection.config = { present: true, path: OPENSPEC_CONFIG_YAML };
|
|
89
|
+
}
|
|
90
|
+
else if (configYmlKind === 'file') {
|
|
91
|
+
inspection.config = { present: true, path: OPENSPEC_CONFIG_YML };
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
inspection.config = { present: false };
|
|
95
|
+
if (configYamlKind !== 'missing' || configYmlKind !== 'missing') {
|
|
96
|
+
inspection.diagnostics.push(missingDirectoryDiagnostic('freespec_config_not_file', 'FreeSpec config path exists but is not a file.', 'freespec.config'));
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
inspection.diagnostics.push(missingDirectoryDiagnostic('freespec_config_missing', 'Missing freespec/config.yaml or freespec/config.yml.', 'freespec.config'));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
await inspectOptionalPlanningDirectory(inspection, storeRoot, 'specs', OPENSPEC_SPECS_DIR, 'freespec_specs_not_directory', 'freespec.specs');
|
|
103
|
+
await inspectOptionalPlanningDirectory(inspection, storeRoot, 'changes', OPENSPEC_CHANGES_DIR, 'freespec_changes_not_directory', 'freespec.changes');
|
|
104
|
+
inspection.archive = { present: false };
|
|
105
|
+
inspection.healthy =
|
|
106
|
+
inspection.present === true &&
|
|
107
|
+
inspection.config.present === true &&
|
|
108
|
+
inspection.diagnostics.length === 0;
|
|
109
|
+
return inspection;
|
|
110
|
+
}
|
|
111
|
+
async function ensureDirectory(storeRoot, relativePath, ledger) {
|
|
112
|
+
const absolutePath = path.join(storeRoot, relativePath);
|
|
113
|
+
const kind = await pathKind(absolutePath);
|
|
114
|
+
if (kind === 'directory')
|
|
115
|
+
return;
|
|
116
|
+
if (kind !== 'missing') {
|
|
117
|
+
throw new Error(`${relativePath}/ exists but is not a directory.`);
|
|
118
|
+
}
|
|
119
|
+
await fs.mkdir(absolutePath, { recursive: true });
|
|
120
|
+
ledger.push({
|
|
121
|
+
relativePath: relativeArtifact(relativePath, 'directory'),
|
|
122
|
+
absolutePath,
|
|
123
|
+
kind: 'directory',
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
async function ensureDefaultConfig(storeRoot, ledger) {
|
|
127
|
+
const configYamlPath = path.join(storeRoot, OPENSPEC_CONFIG_YAML);
|
|
128
|
+
const configYmlPath = path.join(storeRoot, OPENSPEC_CONFIG_YML);
|
|
129
|
+
const yamlKind = await pathKind(configYamlPath);
|
|
130
|
+
const ymlKind = await pathKind(configYmlPath);
|
|
131
|
+
if (yamlKind === 'file' || ymlKind === 'file')
|
|
132
|
+
return;
|
|
133
|
+
if (yamlKind !== 'missing' || ymlKind !== 'missing') {
|
|
134
|
+
throw new Error('FreeSpec config path exists but is not a file.');
|
|
135
|
+
}
|
|
136
|
+
await FileSystemUtils.writeFile(configYamlPath, serializeConfig({ schema: DEFAULT_OPENSPEC_SCHEMA }));
|
|
137
|
+
ledger.push({
|
|
138
|
+
relativePath: relativeArtifact(OPENSPEC_CONFIG_YAML, 'file'),
|
|
139
|
+
absolutePath: configYamlPath,
|
|
140
|
+
kind: 'file',
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
export async function ensureDirectoryAnchor(storeRoot, relativeDir, ledger = []) {
|
|
144
|
+
const directory = path.join(storeRoot, relativeDir);
|
|
145
|
+
if ((await fs.readdir(directory)).length > 0)
|
|
146
|
+
return;
|
|
147
|
+
const relativePath = `${relativeDir}/${DIRECTORY_ANCHOR_FILE_NAME}`;
|
|
148
|
+
const absolutePath = path.join(directory, DIRECTORY_ANCHOR_FILE_NAME);
|
|
149
|
+
try {
|
|
150
|
+
// A file or symlink may appear after readdir. Never replace or follow it.
|
|
151
|
+
await fs.writeFile(absolutePath, '', { encoding: 'utf-8', flag: 'wx' });
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
if (error.code === 'EEXIST')
|
|
155
|
+
return;
|
|
156
|
+
throw error;
|
|
157
|
+
}
|
|
158
|
+
ledger.push({
|
|
159
|
+
relativePath: relativeArtifact(relativePath, 'file'),
|
|
160
|
+
absolutePath,
|
|
161
|
+
kind: 'file',
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
export async function ensureFreeSpecRoot(storeRoot, options = {}) {
|
|
165
|
+
const ledger = [];
|
|
166
|
+
const rootKind = await pathKind(storeRoot);
|
|
167
|
+
if (rootKind === 'missing') {
|
|
168
|
+
await fs.mkdir(storeRoot, { recursive: true });
|
|
169
|
+
}
|
|
170
|
+
else if (rootKind !== 'directory') {
|
|
171
|
+
throw new Error('Store root is not a directory.');
|
|
172
|
+
}
|
|
173
|
+
await ensureDirectory(storeRoot, OPENSPEC_ROOT_DIR, ledger);
|
|
174
|
+
await ensureDefaultConfig(storeRoot, ledger);
|
|
175
|
+
if (options.anchorEmptyDirectories) {
|
|
176
|
+
for (const relativeDir of ANCHORED_OPENSPEC_DIRS) {
|
|
177
|
+
await ensureDirectoryAnchor(storeRoot, relativeDir, ledger);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
inspection: await inspectFreeSpecRoot(storeRoot),
|
|
182
|
+
createdArtifacts: ledger.map((entry) => entry.relativePath),
|
|
183
|
+
createdPaths: ledger,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
export async function rollbackCreatedPaths(entries) {
|
|
187
|
+
for (const entry of [...entries].reverse()) {
|
|
188
|
+
if (entry.kind === 'file') {
|
|
189
|
+
await fs.rm(entry.absolutePath, { force: true }).catch(() => undefined);
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
await fs.rmdir(entry.absolutePath).catch(() => undefined);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=openspec-root.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type PlanningHomeKind = 'repo';
|
|
2
|
+
export interface PlanningHome {
|
|
3
|
+
kind: PlanningHomeKind;
|
|
4
|
+
root: string;
|
|
5
|
+
changesDir: string;
|
|
6
|
+
defaultSchema: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ResolvePlanningHomeOptions {
|
|
9
|
+
startPath?: string;
|
|
10
|
+
allowImplicitRepoRoot?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function findRepoPlanningRootSync(startPath?: string): string | null;
|
|
13
|
+
export declare function resolveCurrentPlanningHomeSync(options?: ResolvePlanningHomeOptions): PlanningHome;
|
|
14
|
+
export declare function getChangeDir(planningHome: PlanningHome, changeName: string): string;
|
|
15
|
+
export declare function formatChangeLocation(planningHome: PlanningHome, changeName: string): string;
|
|
16
|
+
//# sourceMappingURL=planning-home.d.ts.map
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { FileSystemUtils } from '../utils/file-system.js';
|
|
4
|
+
import { changeParentDir, DEFAULT_SCHEMA } from './change-layout.js';
|
|
5
|
+
function pathExistsAsDirectory(candidatePath) {
|
|
6
|
+
try {
|
|
7
|
+
return fs.statSync(candidatePath).isDirectory();
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function getSearchStartDirectory(startPath) {
|
|
14
|
+
const resolved = path.resolve(startPath);
|
|
15
|
+
try {
|
|
16
|
+
const stats = fs.statSync(resolved);
|
|
17
|
+
const searchStart = stats.isDirectory() ? resolved : path.dirname(resolved);
|
|
18
|
+
return FileSystemUtils.canonicalizeExistingPath(searchStart);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return resolved;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function findNearestAncestor(startPath, predicate) {
|
|
25
|
+
let currentDir = getSearchStartDirectory(startPath);
|
|
26
|
+
while (true) {
|
|
27
|
+
if (predicate(currentDir)) {
|
|
28
|
+
return FileSystemUtils.canonicalizeExistingPath(currentDir);
|
|
29
|
+
}
|
|
30
|
+
const parentDir = path.dirname(currentDir);
|
|
31
|
+
if (parentDir === currentDir) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
currentDir = parentDir;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function findRepoPlanningRootSync(startPath = process.cwd()) {
|
|
38
|
+
return findNearestAncestor(startPath, (dirPath) => pathExistsAsDirectory(path.join(dirPath, 'freespec')));
|
|
39
|
+
}
|
|
40
|
+
function repoPlanningHome(repoRoot) {
|
|
41
|
+
return {
|
|
42
|
+
kind: 'repo',
|
|
43
|
+
root: repoRoot,
|
|
44
|
+
changesDir: changeParentDir(repoRoot),
|
|
45
|
+
defaultSchema: DEFAULT_SCHEMA,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export function resolveCurrentPlanningHomeSync(options = {}) {
|
|
49
|
+
const startPath = options.startPath ?? process.cwd();
|
|
50
|
+
const searchStart = getSearchStartDirectory(startPath);
|
|
51
|
+
const repoRoot = findRepoPlanningRootSync(searchStart);
|
|
52
|
+
if (repoRoot) {
|
|
53
|
+
return repoPlanningHome(repoRoot);
|
|
54
|
+
}
|
|
55
|
+
if (options.allowImplicitRepoRoot === false) {
|
|
56
|
+
throw new Error('No FreeSpec planning home found from the current directory.');
|
|
57
|
+
}
|
|
58
|
+
return repoPlanningHome(FileSystemUtils.canonicalizeExistingPath(searchStart));
|
|
59
|
+
}
|
|
60
|
+
export function getChangeDir(planningHome, changeName) {
|
|
61
|
+
return FileSystemUtils.joinPath(planningHome.changesDir, changeName);
|
|
62
|
+
}
|
|
63
|
+
export function formatChangeLocation(planningHome, changeName) {
|
|
64
|
+
// Repo homes always nest changesDir under the root.
|
|
65
|
+
return path.relative(planningHome.root, getChangeDir(planningHome, changeName));
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=planning-home.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Delivery } from './global-config.js';
|
|
2
|
+
import { ALL_WORKFLOWS } from './profiles.js';
|
|
3
|
+
import { type FileView } from './file-view.js';
|
|
4
|
+
type WorkflowId = (typeof ALL_WORKFLOWS)[number];
|
|
5
|
+
/**
|
|
6
|
+
* Maps workflow IDs to their skill directory names.
|
|
7
|
+
*/
|
|
8
|
+
export declare const WORKFLOW_TO_SKILL_DIR: Record<WorkflowId, string>;
|
|
9
|
+
/**
|
|
10
|
+
* Returns tools that are configured via either skills or commands.
|
|
11
|
+
*/
|
|
12
|
+
export declare function getConfiguredToolsForProfileSync(projectPath: string, view?: FileView): string[];
|
|
13
|
+
/**
|
|
14
|
+
* Detects if a single tool has profile/delivery drift against the desired state.
|
|
15
|
+
*
|
|
16
|
+
* This function covers:
|
|
17
|
+
* - required artifacts missing for selected workflows
|
|
18
|
+
* - artifacts that should not exist for the selected delivery mode
|
|
19
|
+
* - artifacts for workflows that were deselected from the current profile
|
|
20
|
+
*/
|
|
21
|
+
export declare function hasToolProfileOrDeliveryDrift(projectPath: string, toolId: string, desiredWorkflows: readonly string[], delivery: Delivery, view?: FileView): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Returns configured tools that currently need a profile/delivery sync.
|
|
24
|
+
*/
|
|
25
|
+
export declare function getToolsNeedingProfileSync(projectPath: string, desiredWorkflows: readonly string[], delivery: Delivery, configuredTools?: readonly string[], view?: FileView): string[];
|
|
26
|
+
/**
|
|
27
|
+
* Detects whether the current project has any profile/delivery drift.
|
|
28
|
+
*/
|
|
29
|
+
export declare function hasProjectConfigDrift(projectPath: string, desiredWorkflows: readonly string[], delivery: Delivery): boolean;
|
|
30
|
+
export {};
|
|
31
|
+
//# sourceMappingURL=profile-sync-drift.d.ts.map
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import { AI_TOOLS } from './config.js';
|
|
4
|
+
import { ALL_WORKFLOWS } from './profiles.js';
|
|
5
|
+
import { CommandAdapterRegistry } from './command-generation/index.js';
|
|
6
|
+
import { getConfiguredTools } from './shared/index.js';
|
|
7
|
+
import { shouldGenerateCommandsForTool, shouldGenerateSkillsForTool, shouldReconcileCommandFilesForTool, shouldRemoveSkillsForTool, } from './command-surface.js';
|
|
8
|
+
import { readSharedSkillTarget } from './shared-skill-target.js';
|
|
9
|
+
import { FileSystemUtils } from '../utils/file-system.js';
|
|
10
|
+
import { DISK_VIEW } from './file-view.js';
|
|
11
|
+
import { isLegacyCodexSkillEquivalentToCurrent } from './shared/skill-content-equivalence.js';
|
|
12
|
+
import { hasGlobalSkillTarget, resolveToolSkillsDir, toolSupportsSkills, } from './shared/skill-paths.js';
|
|
13
|
+
/**
|
|
14
|
+
* Maps workflow IDs to their skill directory names.
|
|
15
|
+
*/
|
|
16
|
+
export const WORKFLOW_TO_SKILL_DIR = {
|
|
17
|
+
'新建': 'freespec-新建',
|
|
18
|
+
'深思': 'freespec-深思',
|
|
19
|
+
'需求': 'freespec-需求',
|
|
20
|
+
'设计': 'freespec-设计',
|
|
21
|
+
'任务': 'freespec-任务',
|
|
22
|
+
'审文档': 'freespec-审文档',
|
|
23
|
+
'用例': 'freespec-用例',
|
|
24
|
+
'验收': 'freespec-验收',
|
|
25
|
+
'写Java': 'freespec-写Java',
|
|
26
|
+
'审Java': 'freespec-审Java',
|
|
27
|
+
};
|
|
28
|
+
function toKnownWorkflows(workflows) {
|
|
29
|
+
return workflows.filter((workflow) => ALL_WORKFLOWS.includes(workflow));
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Returns tools that are configured via either skills or commands.
|
|
33
|
+
*/
|
|
34
|
+
export function getConfiguredToolsForProfileSync(projectPath, view = DISK_VIEW) {
|
|
35
|
+
return getConfiguredTools(projectPath, view);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Detects if a single tool has profile/delivery drift against the desired state.
|
|
39
|
+
*
|
|
40
|
+
* This function covers:
|
|
41
|
+
* - required artifacts missing for selected workflows
|
|
42
|
+
* - artifacts that should not exist for the selected delivery mode
|
|
43
|
+
* - artifacts for workflows that were deselected from the current profile
|
|
44
|
+
*/
|
|
45
|
+
export function hasToolProfileOrDeliveryDrift(projectPath, toolId, desiredWorkflows, delivery, view = DISK_VIEW) {
|
|
46
|
+
const tool = AI_TOOLS.find((t) => t.value === toolId);
|
|
47
|
+
if (!tool || !toolSupportsSkills(tool))
|
|
48
|
+
return false;
|
|
49
|
+
const knownDesiredWorkflows = toKnownWorkflows(desiredWorkflows);
|
|
50
|
+
const desiredWorkflowSet = new Set(knownDesiredWorkflows);
|
|
51
|
+
const skillsDir = resolveToolSkillsDir(projectPath, tool);
|
|
52
|
+
const adapter = CommandAdapterRegistry.get(toolId);
|
|
53
|
+
const shouldGenerateSkills = shouldGenerateSkillsForTool(toolId, delivery);
|
|
54
|
+
const shouldGenerateCommands = shouldGenerateCommandsForTool(toolId, delivery);
|
|
55
|
+
const sharedTarget = tool.skillsDir
|
|
56
|
+
? readSharedSkillTarget(projectPath, tool.skillsDir, view)
|
|
57
|
+
: undefined;
|
|
58
|
+
for (const root of tool.legacySkillsDirs ?? []) {
|
|
59
|
+
for (const workflow of knownDesiredWorkflows) {
|
|
60
|
+
const dirName = WORKFLOW_TO_SKILL_DIR[workflow];
|
|
61
|
+
const legacySkill = path.join(projectPath, root, 'skills', dirName, 'SKILL.md');
|
|
62
|
+
if (!view.exists(legacySkill))
|
|
63
|
+
continue;
|
|
64
|
+
const currentSkill = path.join(skillsDir, dirName, 'SKILL.md');
|
|
65
|
+
if (!view.exists(currentSkill) || sharedTarget !== toolId) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
if (FileSystemUtils.canonicalizeExistingPath(legacySkill) ===
|
|
70
|
+
FileSystemUtils.canonicalizeExistingPath(currentSkill)) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
// Equivalent generated copies are actionable: migration can safely
|
|
74
|
+
// remove the redundant legacy file even when version, line endings,
|
|
75
|
+
// or supported invocation syntax changed. Materially divergent copies
|
|
76
|
+
// stay in place without forcing an update on every run.
|
|
77
|
+
if (isLegacyCodexSkillEquivalentToCurrent(view.read(legacySkill), view.read(currentSkill))) {
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (shouldGenerateSkills) {
|
|
87
|
+
for (const workflow of knownDesiredWorkflows) {
|
|
88
|
+
const dirName = WORKFLOW_TO_SKILL_DIR[workflow];
|
|
89
|
+
const skillFile = path.join(skillsDir, dirName, 'SKILL.md');
|
|
90
|
+
if (!view.exists(skillFile)) {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// Deselecting workflows in a profile should trigger sync.
|
|
95
|
+
for (const workflow of ALL_WORKFLOWS) {
|
|
96
|
+
if (desiredWorkflowSet.has(workflow))
|
|
97
|
+
continue;
|
|
98
|
+
const dirName = WORKFLOW_TO_SKILL_DIR[workflow];
|
|
99
|
+
const skillDir = path.join(skillsDir, dirName);
|
|
100
|
+
if (view.exists(skillDir)) {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else if (shouldRemoveSkillsForTool(toolId, delivery) && !hasGlobalSkillTarget(tool)) {
|
|
106
|
+
for (const workflow of ALL_WORKFLOWS) {
|
|
107
|
+
const dirName = WORKFLOW_TO_SKILL_DIR[workflow];
|
|
108
|
+
const skillDir = path.join(skillsDir, dirName);
|
|
109
|
+
if (view.exists(skillDir)) {
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (shouldGenerateCommands && adapter) {
|
|
115
|
+
for (const workflow of knownDesiredWorkflows) {
|
|
116
|
+
const cmdPath = adapter.getFilePath(workflow);
|
|
117
|
+
const fullPath = path.isAbsolute(cmdPath) ? cmdPath : path.join(projectPath, cmdPath);
|
|
118
|
+
if (!view.exists(fullPath)) {
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// Deselecting workflows in a profile should trigger sync.
|
|
123
|
+
for (const workflow of ALL_WORKFLOWS) {
|
|
124
|
+
if (desiredWorkflowSet.has(workflow))
|
|
125
|
+
continue;
|
|
126
|
+
const cmdPath = adapter.getFilePath(workflow);
|
|
127
|
+
const fullPath = path.isAbsolute(cmdPath) ? cmdPath : path.join(projectPath, cmdPath);
|
|
128
|
+
if (view.exists(fullPath)) {
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else if (shouldReconcileCommandFilesForTool(toolId, delivery) && adapter) {
|
|
134
|
+
for (const workflow of ALL_WORKFLOWS) {
|
|
135
|
+
const cmdPath = adapter.getFilePath(workflow);
|
|
136
|
+
const fullPath = path.isAbsolute(cmdPath) ? cmdPath : path.join(projectPath, cmdPath);
|
|
137
|
+
if (view.exists(fullPath)) {
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Returns configured tools that currently need a profile/delivery sync.
|
|
146
|
+
*/
|
|
147
|
+
export function getToolsNeedingProfileSync(projectPath, desiredWorkflows, delivery, configuredTools, view = DISK_VIEW) {
|
|
148
|
+
const tools = configuredTools
|
|
149
|
+
? [...new Set(configuredTools)]
|
|
150
|
+
: getConfiguredToolsForProfileSync(projectPath, view);
|
|
151
|
+
return tools.filter((toolId) => hasToolProfileOrDeliveryDrift(projectPath, toolId, desiredWorkflows, delivery, view));
|
|
152
|
+
}
|
|
153
|
+
function getInstalledWorkflowsForTool(projectPath, toolId, options) {
|
|
154
|
+
const tool = AI_TOOLS.find((t) => t.value === toolId);
|
|
155
|
+
if (!tool || !toolSupportsSkills(tool))
|
|
156
|
+
return [];
|
|
157
|
+
const installed = new Set();
|
|
158
|
+
const skillsDir = resolveToolSkillsDir(projectPath, tool);
|
|
159
|
+
if (options.includeSkills) {
|
|
160
|
+
for (const workflow of ALL_WORKFLOWS) {
|
|
161
|
+
const dirName = WORKFLOW_TO_SKILL_DIR[workflow];
|
|
162
|
+
const skillFile = path.join(skillsDir, dirName, 'SKILL.md');
|
|
163
|
+
if (fs.existsSync(skillFile)) {
|
|
164
|
+
installed.add(workflow);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (options.includeCommands) {
|
|
169
|
+
const adapter = CommandAdapterRegistry.get(toolId);
|
|
170
|
+
if (adapter) {
|
|
171
|
+
for (const workflow of ALL_WORKFLOWS) {
|
|
172
|
+
const cmdPath = adapter.getFilePath(workflow);
|
|
173
|
+
const fullPath = path.isAbsolute(cmdPath) ? cmdPath : path.join(projectPath, cmdPath);
|
|
174
|
+
if (fs.existsSync(fullPath)) {
|
|
175
|
+
installed.add(workflow);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return [...installed];
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Detects whether the current project has any profile/delivery drift.
|
|
184
|
+
*/
|
|
185
|
+
export function hasProjectConfigDrift(projectPath, desiredWorkflows, delivery) {
|
|
186
|
+
const configuredTools = getConfiguredToolsForProfileSync(projectPath);
|
|
187
|
+
if (getToolsNeedingProfileSync(projectPath, desiredWorkflows, delivery, configuredTools).length > 0) {
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
const desiredSet = new Set(toKnownWorkflows(desiredWorkflows));
|
|
191
|
+
for (const toolId of configuredTools) {
|
|
192
|
+
const includeSkills = shouldGenerateSkillsForTool(toolId, delivery);
|
|
193
|
+
const includeCommands = shouldGenerateCommandsForTool(toolId, delivery);
|
|
194
|
+
const installed = getInstalledWorkflowsForTool(projectPath, toolId, { includeSkills, includeCommands });
|
|
195
|
+
if (installed.some((workflow) => !desiredSet.has(workflow))) {
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=profile-sync-drift.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Profiles control which workflows are installed.
|
|
3
|
+
* Delivery (in global config) controls how they are installed.
|
|
4
|
+
*/
|
|
5
|
+
import type { Profile } from './global-config.js';
|
|
6
|
+
/** Workflows installed by the core profile, and the full set this build ships. */
|
|
7
|
+
export declare const CORE_WORKFLOWS: readonly ["新建", "深思", "需求", "设计", "任务", "审文档", "用例", "验收", "写Java", "审Java"];
|
|
8
|
+
export declare const ALL_WORKFLOWS: readonly ["新建", "深思", "需求", "设计", "任务", "审文档", "用例", "验收", "写Java", "审Java"];
|
|
9
|
+
export type WorkflowId = (typeof ALL_WORKFLOWS)[number];
|
|
10
|
+
export type CoreWorkflowId = (typeof CORE_WORKFLOWS)[number];
|
|
11
|
+
/**
|
|
12
|
+
* Resolves which workflows should be active.
|
|
13
|
+
*
|
|
14
|
+
* - 'core' returns the full set.
|
|
15
|
+
* - 'custom' returns the requested ids that exist in that set.
|
|
16
|
+
* Unknown ids are dropped. An empty result stays empty.
|
|
17
|
+
*/
|
|
18
|
+
export declare function getProfileWorkflows(profile: Profile, customWorkflows?: string[]): readonly string[];
|
|
19
|
+
//# sourceMappingURL=profiles.d.ts.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Profiles control which workflows are installed.
|
|
3
|
+
* Delivery (in global config) controls how they are installed.
|
|
4
|
+
*/
|
|
5
|
+
/** Workflows installed by the core profile, and the full set this build ships. */
|
|
6
|
+
export const CORE_WORKFLOWS = [
|
|
7
|
+
'新建',
|
|
8
|
+
'深思',
|
|
9
|
+
'需求',
|
|
10
|
+
'设计',
|
|
11
|
+
'任务',
|
|
12
|
+
'审文档',
|
|
13
|
+
'用例',
|
|
14
|
+
'验收',
|
|
15
|
+
'写Java',
|
|
16
|
+
'审Java',
|
|
17
|
+
];
|
|
18
|
+
export const ALL_WORKFLOWS = CORE_WORKFLOWS;
|
|
19
|
+
/**
|
|
20
|
+
* Resolves which workflows should be active.
|
|
21
|
+
*
|
|
22
|
+
* - 'core' returns the full set.
|
|
23
|
+
* - 'custom' returns the requested ids that exist in that set.
|
|
24
|
+
* Unknown ids are dropped. An empty result stays empty.
|
|
25
|
+
*/
|
|
26
|
+
export function getProfileWorkflows(profile, customWorkflows) {
|
|
27
|
+
if (profile === 'custom') {
|
|
28
|
+
const known = new Set(ALL_WORKFLOWS);
|
|
29
|
+
return [...new Set((customWorkflows ?? []).filter((workflow) => known.has(workflow)))];
|
|
30
|
+
}
|
|
31
|
+
return CORE_WORKFLOWS;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=profiles.js.map
|