@dynamicworks/br-openspec 2.0.0 → 2.1.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 +11 -2
- package/README.pt-BR.md +11 -2
- package/dist/commands/config.js +4 -0
- package/dist/commands/schema.js +21 -21
- package/dist/core/artifact-graph/instruction-loader.js +4 -4
- package/dist/core/artifact-graph/schema.js +5 -4
- package/dist/core/completions/factory.js +3 -2
- package/dist/core/completions/installers/fish-installer.js +13 -12
- package/dist/core/completions/installers/powershell-installer.js +16 -17
- package/dist/core/completions/installers/zsh-installer.d.ts +0 -8
- package/dist/core/completions/installers/zsh-installer.js +4 -32
- package/dist/core/config.js +3 -2
- package/dist/core/global-config.d.ts +6 -1
- package/dist/core/global-config.js +15 -16
- package/dist/core/parsers/change-parser.js +7 -6
- package/dist/core/parsers/requirement-blocks.js +5 -5
- package/dist/core/parsers/spec-structure.js +1 -1
- package/dist/core/profile-sync-drift.js +1 -0
- package/dist/core/profiles.d.ts +2 -2
- package/dist/core/profiles.js +2 -1
- package/dist/core/project-config.js +12 -13
- package/dist/core/shared/skill-generation.js +3 -1
- package/dist/core/shared/tool-detection.d.ts +2 -2
- package/dist/core/shared/tool-detection.js +2 -0
- package/dist/core/specs-apply.js +38 -39
- package/dist/core/templates/skill-templates.d.ts +1 -1
- package/dist/core/templates/skill-templates.js +1 -1
- package/dist/core/templates/workflows/code-review.d.ts +10 -0
- package/dist/core/templates/workflows/code-review.js +21 -0
- package/dist/core/templates/workflows/sync-specs.js +2 -2
- package/dist/core/tools-manager.js +3 -2
- package/dist/core/update.d.ts +6 -0
- package/dist/core/update.js +21 -0
- package/dist/core/validation/validator.js +2 -2
- package/dist/messages/index.d.ts +145 -2
- package/dist/messages/index.js +320 -12
- package/dist/utils/change-metadata.js +8 -7
- package/dist/utils/change-utils.js +12 -11
- package/package.json +1 -1
- package/schemas/spec-driven/schema.yaml +78 -78
- package/schemas/spec-driven/templates/design.md +5 -5
- package/schemas/spec-driven/templates/proposal.md +9 -9
- package/schemas/spec-driven/templates/spec.md +5 -5
- package/schemas/spec-driven/templates/tasks.md +6 -6
- package/dist/core/templates/workflows/upstream-sync.d.ts +0 -10
- package/dist/core/templates/workflows/upstream-sync.js +0 -116
package/README.md
CHANGED
|
@@ -97,7 +97,7 @@ openspec init
|
|
|
97
97
|
|
|
98
98
|
Now tell your AI: `/opsx:propose <what-you-want-to-build>`
|
|
99
99
|
|
|
100
|
-
If you want the expanded workflow (`/opsx:new`, `/opsx:continue`, `/opsx:ff`, `/opsx:verify`, `/opsx:
|
|
100
|
+
If you want the expanded workflow (`/opsx:new`, `/opsx:continue`, `/opsx:ff`, `/opsx:verify`, `/opsx:bulk-archive`, `/opsx:onboard`), select it with `openspec config profile` and apply with `openspec update`.
|
|
101
101
|
|
|
102
102
|
> [!NOTE]
|
|
103
103
|
> Not sure if your tool is supported? [View the full list](docs/supported-tools.md) – we support 25+ tools and growing.
|
|
@@ -116,6 +116,13 @@ If you want the expanded workflow (`/opsx:new`, `/opsx:continue`, `/opsx:ff`, `/
|
|
|
116
116
|
→ **[Customization](docs/customization.md)**: make it yours
|
|
117
117
|
|
|
118
118
|
|
|
119
|
+
## Community schemas
|
|
120
|
+
|
|
121
|
+
Third-party schema bundles distributed via standalone repositories — these provide opinionated workflows that integrate BR-OpenSpec with other tools, similar to how [github/spec-kit's community extension catalog](https://github.com/github/spec-kit/tree/main/extensions) handles tool integrations.
|
|
122
|
+
|
|
123
|
+
→ **[Browse the catalog](docs/customization.md#community-schemas)** in the customization docs.
|
|
124
|
+
|
|
125
|
+
|
|
119
126
|
## Why BR-OpenSpec?
|
|
120
127
|
|
|
121
128
|
AI coding assistants are powerful but unpredictable when requirements live only in chat history. BR-OpenSpec adds a lightweight spec layer so you agree on what to build before any code is written.
|
|
@@ -161,10 +168,12 @@ openspec tools --remove windsurf
|
|
|
161
168
|
|
|
162
169
|
## Usage Notes
|
|
163
170
|
|
|
164
|
-
**Model selection**: BR-OpenSpec works best with high-reasoning models. We recommend
|
|
171
|
+
**Model selection**: BR-OpenSpec works best with high-reasoning models. We recommend Codex 5.5 and Opus 4.8 for both planning and implementation.
|
|
165
172
|
|
|
166
173
|
**Context hygiene**: BR-OpenSpec benefits from a clean context window. Clear your context before starting implementation and maintain good context hygiene throughout your session.
|
|
167
174
|
|
|
175
|
+
**Spec keywords stay in English**: BR-OpenSpec is PT-BR first, but the spec format is a protocol read by the tooling. Structural markers (`## ADDED Requirements`, `### Requirement:`, `#### Scenario:`) and normative/scenario keywords (RFC 2119 — `MUST`, `SHALL`, `SHOULD`, `MAY`, … — plus `WHEN`, `THEN`, `AND`, `GIVEN`, `ELSE`) are always written in English and UPPERCASE; only the descriptive text is in Portuguese. Translating these keywords breaks `openspec validate`. See the full list in [AGENTS.md](AGENTS.md#reserved-english-terms-never-translate).
|
|
176
|
+
|
|
168
177
|
## Contributing
|
|
169
178
|
|
|
170
179
|
**Small fixes** — Bug fixes, typo corrections, and minor improvements can be submitted directly as PRs.
|
package/README.pt-BR.md
CHANGED
|
@@ -97,7 +97,7 @@ openspec init
|
|
|
97
97
|
|
|
98
98
|
Agora diga à sua IA: `/opsx:propose <o-que-você-quer-construir>`
|
|
99
99
|
|
|
100
|
-
Se você quiser o fluxo de trabalho expandido (`/opsx:new`, `/opsx:continue`, `/opsx:ff`, `/opsx:verify`, `/opsx:
|
|
100
|
+
Se você quiser o fluxo de trabalho expandido (`/opsx:new`, `/opsx:continue`, `/opsx:ff`, `/opsx:verify`, `/opsx:bulk-archive`, `/opsx:onboard`), selecione-o com `openspec config profile` e aplique com `openspec update`.
|
|
101
101
|
|
|
102
102
|
> [!NOTE]
|
|
103
103
|
> Não tem certeza se sua ferramenta é suportada? [Veja a lista completa](docs/pt-BR/supported-tools.md) – suportamos mais de 25 ferramentas e crescendo.
|
|
@@ -116,6 +116,13 @@ Se você quiser o fluxo de trabalho expandido (`/opsx:new`, `/opsx:continue`, `/
|
|
|
116
116
|
→ **[Personalização](docs/pt-BR/customization.md)**: faça do seu jeito
|
|
117
117
|
|
|
118
118
|
|
|
119
|
+
## Schemas da comunidade
|
|
120
|
+
|
|
121
|
+
Bundles de schema de terceiros distribuídos em repositórios independentes — oferecem fluxos de trabalho opinativos que integram o BR-OpenSpec a outras ferramentas, de forma semelhante a como o [catálogo de extensões da comunidade do github/spec-kit](https://github.com/github/spec-kit/tree/main/extensions) trata integrações de ferramentas.
|
|
122
|
+
|
|
123
|
+
→ **[Veja o catálogo](docs/pt-BR/customization.md#schemas-da-comunidade)** na documentação de personalização.
|
|
124
|
+
|
|
125
|
+
|
|
119
126
|
## Por que o BR-OpenSpec?
|
|
120
127
|
|
|
121
128
|
Assistentes de codificação com IA são poderosos, mas imprevisíveis quando os requisitos vivem apenas no histórico do chat. O BR-OpenSpec adiciona uma camada leve de especificação para que você concorde sobre o que construir antes de qualquer código ser escrito.
|
|
@@ -163,10 +170,12 @@ openspec tools --remove windsurf
|
|
|
163
170
|
|
|
164
171
|
## Notas de Uso
|
|
165
172
|
|
|
166
|
-
**Seleção de modelo**: O BR-OpenSpec funciona melhor com modelos de alto raciocínio. Recomendamos
|
|
173
|
+
**Seleção de modelo**: O BR-OpenSpec funciona melhor com modelos de alto raciocínio. Recomendamos Codex 5.5 e Opus 4.8 tanto para planejamento quanto para implementação.
|
|
167
174
|
|
|
168
175
|
**Higiene de contexto**: O BR-OpenSpec se beneficia de uma janela de contexto limpa. Limpe seu contexto antes de iniciar a implementação e mantenha uma boa higiene de contexto ao longo da sua sessão.
|
|
169
176
|
|
|
177
|
+
**Palavras-chave do formato de spec ficam em inglês**: O BR-OpenSpec é PT-BR first, mas o formato de spec é um protocolo lido pelas ferramentas. Os marcadores estruturais (`## ADDED Requirements`, `### Requirement:`, `#### Scenario:`) e as palavras-chave normativas/de cenário (RFC 2119 — `MUST`, `SHALL`, `SHOULD`, `MAY`, … — além de `WHEN`, `THEN`, `AND`, `GIVEN`, `ELSE`) são sempre escritas em inglês e em CAIXA ALTA; apenas o texto descritivo fica em português. Traduzir essas palavras-chave quebra o `openspec validate`. Veja a lista completa em [AGENTS.md](AGENTS.md#reserved-english-terms-never-translate).
|
|
178
|
+
|
|
170
179
|
## Contribuindo
|
|
171
180
|
|
|
172
181
|
**Pequenas correções** — Correções de bugs, erros de digitação e melhorias menores podem ser enviadas diretamente como PRs.
|
package/dist/commands/config.js
CHANGED
|
@@ -48,6 +48,10 @@ const WORKFLOW_PROMPT_META = {
|
|
|
48
48
|
name: CONFIG_MESSAGES.workflowVerifyName,
|
|
49
49
|
description: CONFIG_MESSAGES.workflowVerifyDesc,
|
|
50
50
|
},
|
|
51
|
+
'code-review': {
|
|
52
|
+
name: CONFIG_MESSAGES.workflowCodeReviewName,
|
|
53
|
+
description: CONFIG_MESSAGES.workflowCodeReviewDesc,
|
|
54
|
+
},
|
|
51
55
|
onboard: {
|
|
52
56
|
name: CONFIG_MESSAGES.workflowOnboardName,
|
|
53
57
|
description: CONFIG_MESSAGES.workflowOnboardDesc,
|
package/dist/commands/schema.js
CHANGED
|
@@ -797,72 +797,72 @@ function createDefaultTemplate(artifactId) {
|
|
|
797
797
|
case 'proposal':
|
|
798
798
|
return `## Why
|
|
799
799
|
|
|
800
|
-
<!--
|
|
800
|
+
<!-- Descreva a motivação para esta mudança -->
|
|
801
801
|
|
|
802
802
|
## What Changes
|
|
803
803
|
|
|
804
|
-
<!--
|
|
804
|
+
<!-- Descreva o que vai mudar -->
|
|
805
805
|
|
|
806
806
|
## Capabilities
|
|
807
807
|
|
|
808
808
|
### New Capabilities
|
|
809
|
-
<!--
|
|
809
|
+
<!-- Liste as novas capabilities -->
|
|
810
810
|
|
|
811
811
|
### Modified Capabilities
|
|
812
|
-
<!--
|
|
812
|
+
<!-- Liste as capabilities modificadas -->
|
|
813
813
|
|
|
814
814
|
## Impact
|
|
815
815
|
|
|
816
|
-
<!--
|
|
816
|
+
<!-- Descreva o impacto sobre a funcionalidade existente -->
|
|
817
817
|
`;
|
|
818
818
|
case 'specs':
|
|
819
819
|
return `## ADDED Requirements
|
|
820
820
|
|
|
821
|
-
### Requirement:
|
|
821
|
+
### Requirement: Requisito de exemplo
|
|
822
822
|
|
|
823
|
-
|
|
823
|
+
Descrição do requisito.
|
|
824
824
|
|
|
825
|
-
#### Scenario:
|
|
826
|
-
- **WHEN**
|
|
827
|
-
- **THEN**
|
|
825
|
+
#### Scenario: Cenário de exemplo
|
|
826
|
+
- **WHEN** alguma condição
|
|
827
|
+
- **THEN** algum resultado
|
|
828
828
|
`;
|
|
829
829
|
case 'design':
|
|
830
830
|
return `## Context
|
|
831
831
|
|
|
832
|
-
<!--
|
|
832
|
+
<!-- Contexto e antecedentes -->
|
|
833
833
|
|
|
834
834
|
## Goals / Non-Goals
|
|
835
835
|
|
|
836
836
|
**Goals:**
|
|
837
|
-
<!--
|
|
837
|
+
<!-- Liste os objetivos -->
|
|
838
838
|
|
|
839
839
|
**Non-Goals:**
|
|
840
|
-
<!--
|
|
840
|
+
<!-- Liste os não-objetivos -->
|
|
841
841
|
|
|
842
842
|
## Decisions
|
|
843
843
|
|
|
844
|
-
### 1.
|
|
844
|
+
### 1. Nome da Decisão
|
|
845
845
|
|
|
846
|
-
|
|
846
|
+
Descrição e justificativa.
|
|
847
847
|
|
|
848
848
|
**Alternatives considered:**
|
|
849
|
-
-
|
|
849
|
+
- Alternativa 1: Rejeitada porque...
|
|
850
850
|
|
|
851
851
|
## Risks / Trade-offs
|
|
852
852
|
|
|
853
|
-
<!--
|
|
853
|
+
<!-- Liste os riscos e trade-offs -->
|
|
854
854
|
`;
|
|
855
855
|
case 'tasks':
|
|
856
856
|
return `## Implementation Tasks
|
|
857
857
|
|
|
858
|
-
- [ ]
|
|
859
|
-
- [ ]
|
|
860
|
-
- [ ]
|
|
858
|
+
- [ ] Tarefa 1
|
|
859
|
+
- [ ] Tarefa 2
|
|
860
|
+
- [ ] Tarefa 3
|
|
861
861
|
`;
|
|
862
862
|
default:
|
|
863
863
|
return `## ${artifactId}
|
|
864
864
|
|
|
865
|
-
<!--
|
|
865
|
+
<!-- Adicione o conteúdo aqui -->
|
|
866
866
|
`;
|
|
867
867
|
}
|
|
868
868
|
}
|
|
@@ -4,7 +4,7 @@ import { getSchemaDir, resolveSchema } from './resolver.js';
|
|
|
4
4
|
import { ArtifactGraph } from './graph.js';
|
|
5
5
|
import { detectCompleted } from './state.js';
|
|
6
6
|
import { resolveSchemaForChange } from '../../utils/change-metadata.js';
|
|
7
|
-
import { WORKFLOW_MESSAGES } from '../../messages/index.js';
|
|
7
|
+
import { WORKFLOW_MESSAGES, ARTIFACT_GRAPH_MESSAGES } from '../../messages/index.js';
|
|
8
8
|
import { FileSystemUtils } from '../../utils/file-system.js';
|
|
9
9
|
import { readProjectConfig, validateConfigRules } from '../project-config.js';
|
|
10
10
|
// Session-level cache for validation warnings (avoid repeating same warnings)
|
|
@@ -36,7 +36,7 @@ export function loadTemplate(schemaName, templatePath, projectRoot) {
|
|
|
36
36
|
}
|
|
37
37
|
const templatePathOnDisk = path.join(schemaDir, 'templates', templatePath);
|
|
38
38
|
if (!fs.existsSync(templatePathOnDisk)) {
|
|
39
|
-
throw new TemplateLoadError(
|
|
39
|
+
throw new TemplateLoadError(ARTIFACT_GRAPH_MESSAGES.templateNotFound(templatePathOnDisk), templatePathOnDisk);
|
|
40
40
|
}
|
|
41
41
|
const fullPath = FileSystemUtils.canonicalizeExistingPath(templatePathOnDisk);
|
|
42
42
|
try {
|
|
@@ -44,7 +44,7 @@ export function loadTemplate(schemaName, templatePath, projectRoot) {
|
|
|
44
44
|
}
|
|
45
45
|
catch (err) {
|
|
46
46
|
const ioError = err instanceof Error ? err : new Error(String(err));
|
|
47
|
-
throw new TemplateLoadError(
|
|
47
|
+
throw new TemplateLoadError(ARTIFACT_GRAPH_MESSAGES.failedToReadTemplate(ioError.message), fullPath);
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
/**
|
|
@@ -93,7 +93,7 @@ export function loadChangeContext(projectRoot, changeName, schemaName) {
|
|
|
93
93
|
export function generateInstructions(context, artifactId, projectRoot) {
|
|
94
94
|
const artifact = context.graph.getArtifact(artifactId);
|
|
95
95
|
if (!artifact) {
|
|
96
|
-
throw new Error(
|
|
96
|
+
throw new Error(ARTIFACT_GRAPH_MESSAGES.artifactNotFound(artifactId, context.schemaName));
|
|
97
97
|
}
|
|
98
98
|
const templateContent = loadTemplate(context.schemaName, artifact.template, context.projectRoot);
|
|
99
99
|
const dependencies = getDependencyInfo(artifact, context.graph, context.completed);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from 'node:fs';
|
|
2
2
|
import { parse as parseYaml } from 'yaml';
|
|
3
3
|
import { SchemaYamlSchema } from './types.js';
|
|
4
|
+
import { ARTIFACT_GRAPH_MESSAGES } from '../../messages/index.js';
|
|
4
5
|
export class SchemaValidationError extends Error {
|
|
5
6
|
constructor(message) {
|
|
6
7
|
super(message);
|
|
@@ -23,7 +24,7 @@ export function parseSchema(yamlContent) {
|
|
|
23
24
|
const result = SchemaYamlSchema.safeParse(parsed);
|
|
24
25
|
if (!result.success) {
|
|
25
26
|
const errors = result.error.issues.map(e => `${e.path.join('.')}: ${e.message}`).join(', ');
|
|
26
|
-
throw new SchemaValidationError(
|
|
27
|
+
throw new SchemaValidationError(ARTIFACT_GRAPH_MESSAGES.invalidSchema(errors));
|
|
27
28
|
}
|
|
28
29
|
const schema = result.data;
|
|
29
30
|
// Check for duplicate artifact IDs
|
|
@@ -41,7 +42,7 @@ function validateNoDuplicateIds(artifacts) {
|
|
|
41
42
|
const seen = new Set();
|
|
42
43
|
for (const artifact of artifacts) {
|
|
43
44
|
if (seen.has(artifact.id)) {
|
|
44
|
-
throw new SchemaValidationError(
|
|
45
|
+
throw new SchemaValidationError(ARTIFACT_GRAPH_MESSAGES.duplicateArtifactId(artifact.id));
|
|
45
46
|
}
|
|
46
47
|
seen.add(artifact.id);
|
|
47
48
|
}
|
|
@@ -54,7 +55,7 @@ function validateRequiresReferences(artifacts) {
|
|
|
54
55
|
for (const artifact of artifacts) {
|
|
55
56
|
for (const req of artifact.requires) {
|
|
56
57
|
if (!validIds.has(req)) {
|
|
57
|
-
throw new SchemaValidationError(
|
|
58
|
+
throw new SchemaValidationError(ARTIFACT_GRAPH_MESSAGES.invalidDependencyReference(artifact.id, req));
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
}
|
|
@@ -100,7 +101,7 @@ function validateNoCycles(artifacts) {
|
|
|
100
101
|
if (!visited.has(artifact.id)) {
|
|
101
102
|
const cycle = dfs(artifact.id);
|
|
102
103
|
if (cycle) {
|
|
103
|
-
throw new SchemaValidationError(
|
|
104
|
+
throw new SchemaValidationError(ARTIFACT_GRAPH_MESSAGES.cyclicDependency(cycle));
|
|
104
105
|
}
|
|
105
106
|
}
|
|
106
107
|
}
|
|
@@ -6,6 +6,7 @@ import { ZshInstaller } from './installers/zsh-installer.js';
|
|
|
6
6
|
import { BashInstaller } from './installers/bash-installer.js';
|
|
7
7
|
import { FishInstaller } from './installers/fish-installer.js';
|
|
8
8
|
import { PowerShellInstaller } from './installers/powershell-installer.js';
|
|
9
|
+
import { COMPLETIONS_FACTORY_MESSAGES } from '../../messages/index.js';
|
|
9
10
|
/**
|
|
10
11
|
* Factory for creating completion generators and installers
|
|
11
12
|
* This design makes it easy to add support for additional shells
|
|
@@ -30,7 +31,7 @@ export class CompletionFactory {
|
|
|
30
31
|
case 'powershell':
|
|
31
32
|
return new PowerShellGenerator();
|
|
32
33
|
default:
|
|
33
|
-
throw new Error(
|
|
34
|
+
throw new Error(COMPLETIONS_FACTORY_MESSAGES.unsupportedShell(shell));
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
37
|
/**
|
|
@@ -51,7 +52,7 @@ export class CompletionFactory {
|
|
|
51
52
|
case 'powershell':
|
|
52
53
|
return new PowerShellInstaller();
|
|
53
54
|
default:
|
|
54
|
-
throw new Error(
|
|
55
|
+
throw new Error(COMPLETIONS_FACTORY_MESSAGES.unsupportedShell(shell));
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
58
|
/**
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { promises as fs } from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import os from 'os';
|
|
4
|
+
import { COMPLETION_MESSAGES } from '../../../messages/index.js';
|
|
4
5
|
/**
|
|
5
6
|
* Installer for Fish completion scripts.
|
|
6
7
|
* Fish automatically loads completions from ~/.config/fish/completions/
|
|
@@ -56,10 +57,10 @@ export class FishInstaller {
|
|
|
56
57
|
return {
|
|
57
58
|
success: true,
|
|
58
59
|
installedPath: targetPath,
|
|
59
|
-
message:
|
|
60
|
+
message: COMPLETION_MESSAGES.fishAlreadyInstalled,
|
|
60
61
|
instructions: [
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
COMPLETION_MESSAGES.fishAlreadyInstalledDetail,
|
|
63
|
+
COMPLETION_MESSAGES.fishAutoLoadsHint,
|
|
63
64
|
],
|
|
64
65
|
};
|
|
65
66
|
}
|
|
@@ -81,11 +82,11 @@ export class FishInstaller {
|
|
|
81
82
|
let message;
|
|
82
83
|
if (isUpdate) {
|
|
83
84
|
message = backupPath
|
|
84
|
-
?
|
|
85
|
-
:
|
|
85
|
+
? COMPLETION_MESSAGES.fishUpdatedWithBackup
|
|
86
|
+
: COMPLETION_MESSAGES.fishUpdated;
|
|
86
87
|
}
|
|
87
88
|
else {
|
|
88
|
-
message =
|
|
89
|
+
message = COMPLETION_MESSAGES.fishInstalled;
|
|
89
90
|
}
|
|
90
91
|
return {
|
|
91
92
|
success: true,
|
|
@@ -93,15 +94,15 @@ export class FishInstaller {
|
|
|
93
94
|
backupPath,
|
|
94
95
|
message,
|
|
95
96
|
instructions: [
|
|
96
|
-
|
|
97
|
-
|
|
97
|
+
COMPLETION_MESSAGES.fishAutoLoadsDir,
|
|
98
|
+
COMPLETION_MESSAGES.fishAvailableImmediately,
|
|
98
99
|
],
|
|
99
100
|
};
|
|
100
101
|
}
|
|
101
102
|
catch (error) {
|
|
102
103
|
return {
|
|
103
104
|
success: false,
|
|
104
|
-
message:
|
|
105
|
+
message: COMPLETION_MESSAGES.fishFailedToInstall(error instanceof Error ? error.message : String(error)),
|
|
105
106
|
};
|
|
106
107
|
}
|
|
107
108
|
}
|
|
@@ -122,20 +123,20 @@ export class FishInstaller {
|
|
|
122
123
|
catch {
|
|
123
124
|
return {
|
|
124
125
|
success: false,
|
|
125
|
-
message:
|
|
126
|
+
message: COMPLETION_MESSAGES.fishNotInstalled,
|
|
126
127
|
};
|
|
127
128
|
}
|
|
128
129
|
// Remove the completion script
|
|
129
130
|
await fs.unlink(targetPath);
|
|
130
131
|
return {
|
|
131
132
|
success: true,
|
|
132
|
-
message:
|
|
133
|
+
message: COMPLETION_MESSAGES.fishUninstalled,
|
|
133
134
|
};
|
|
134
135
|
}
|
|
135
136
|
catch (error) {
|
|
136
137
|
return {
|
|
137
138
|
success: false,
|
|
138
|
-
message:
|
|
139
|
+
message: COMPLETION_MESSAGES.fishFailedToUninstall(error instanceof Error ? error.message : String(error)),
|
|
139
140
|
};
|
|
140
141
|
}
|
|
141
142
|
}
|
|
@@ -29,8 +29,7 @@ export class PowerShellInstaller {
|
|
|
29
29
|
}
|
|
30
30
|
// UTF-16 BE BOM: FE FF — not natively supported by Node
|
|
31
31
|
if (buffer.length >= 2 && buffer[0] === 0xfe && buffer[1] === 0xff) {
|
|
32
|
-
throw new Error(
|
|
33
|
-
'Please re-save as UTF-8 or UTF-16 LE, then retry.');
|
|
32
|
+
throw new Error(COMPLETION_MESSAGES.powershellUtf16BEUnsupported);
|
|
34
33
|
}
|
|
35
34
|
// UTF-8 BOM: EF BB BF
|
|
36
35
|
if (buffer.length >= 3 && buffer[0] === 0xef && buffer[1] === 0xbb && buffer[2] === 0xbf) {
|
|
@@ -274,10 +273,10 @@ export class PowerShellInstaller {
|
|
|
274
273
|
return {
|
|
275
274
|
success: true,
|
|
276
275
|
installedPath: targetPath,
|
|
277
|
-
message:
|
|
276
|
+
message: COMPLETION_MESSAGES.powershellAlreadyInstalled,
|
|
278
277
|
instructions: [
|
|
279
|
-
|
|
280
|
-
|
|
278
|
+
COMPLETION_MESSAGES.powershellAlreadyInstalledDetail,
|
|
279
|
+
COMPLETION_MESSAGES.powershellAlreadyInstalledHint,
|
|
281
280
|
],
|
|
282
281
|
};
|
|
283
282
|
}
|
|
@@ -315,13 +314,13 @@ export class PowerShellInstaller {
|
|
|
315
314
|
let message;
|
|
316
315
|
if (isUpdate) {
|
|
317
316
|
message = backupPath
|
|
318
|
-
?
|
|
319
|
-
:
|
|
317
|
+
? COMPLETION_MESSAGES.powershellUpdatedWithBackup
|
|
318
|
+
: COMPLETION_MESSAGES.powershellUpdated;
|
|
320
319
|
}
|
|
321
320
|
else {
|
|
322
321
|
message = profileConfigured
|
|
323
|
-
?
|
|
324
|
-
:
|
|
322
|
+
? COMPLETION_MESSAGES.powershellInstalledWithProfile
|
|
323
|
+
: COMPLETION_MESSAGES.powershellInstalled;
|
|
325
324
|
}
|
|
326
325
|
return {
|
|
327
326
|
success: true,
|
|
@@ -335,7 +334,7 @@ export class PowerShellInstaller {
|
|
|
335
334
|
catch (error) {
|
|
336
335
|
return {
|
|
337
336
|
success: false,
|
|
338
|
-
message:
|
|
337
|
+
message: COMPLETION_MESSAGES.powershellFailedToInstall(error instanceof Error ? error.message : String(error)),
|
|
339
338
|
};
|
|
340
339
|
}
|
|
341
340
|
}
|
|
@@ -348,16 +347,16 @@ export class PowerShellInstaller {
|
|
|
348
347
|
generateInstructions(installedPath) {
|
|
349
348
|
const profilePath = this.getProfilePath();
|
|
350
349
|
return [
|
|
351
|
-
|
|
350
|
+
COMPLETION_MESSAGES.powershellScriptInstalled,
|
|
352
351
|
'',
|
|
353
|
-
|
|
352
|
+
COMPLETION_MESSAGES.powershellEnableCompletions(profilePath),
|
|
354
353
|
'',
|
|
355
|
-
|
|
354
|
+
` ${COMPLETION_MESSAGES.powershellSourceComment}`,
|
|
356
355
|
` if (Test-Path "${installedPath}") {`,
|
|
357
356
|
` . "${installedPath}"`,
|
|
358
357
|
' }',
|
|
359
358
|
'',
|
|
360
|
-
|
|
359
|
+
COMPLETION_MESSAGES.powershellThenRestart,
|
|
361
360
|
];
|
|
362
361
|
}
|
|
363
362
|
/**
|
|
@@ -377,7 +376,7 @@ export class PowerShellInstaller {
|
|
|
377
376
|
catch {
|
|
378
377
|
return {
|
|
379
378
|
success: false,
|
|
380
|
-
message:
|
|
379
|
+
message: COMPLETION_MESSAGES.powershellNotInstalled,
|
|
381
380
|
};
|
|
382
381
|
}
|
|
383
382
|
// Remove the completion script
|
|
@@ -386,13 +385,13 @@ export class PowerShellInstaller {
|
|
|
386
385
|
await this.removeProfileConfig();
|
|
387
386
|
return {
|
|
388
387
|
success: true,
|
|
389
|
-
message:
|
|
388
|
+
message: COMPLETION_MESSAGES.powershellUninstalled,
|
|
390
389
|
};
|
|
391
390
|
}
|
|
392
391
|
catch (error) {
|
|
393
392
|
return {
|
|
394
393
|
success: false,
|
|
395
|
-
message:
|
|
394
|
+
message: COMPLETION_MESSAGES.powershellFailedToUninstall(error instanceof Error ? error.message : String(error)),
|
|
396
395
|
};
|
|
397
396
|
}
|
|
398
397
|
}
|
|
@@ -59,14 +59,6 @@ export declare class ZshInstaller {
|
|
|
59
59
|
* @returns true if .zshrc exists and has markers
|
|
60
60
|
*/
|
|
61
61
|
private hasZshrcConfig;
|
|
62
|
-
/**
|
|
63
|
-
* Check if fpath configuration is needed for a given directory
|
|
64
|
-
* Used to verify if Oh My Zsh (or other) completions directory is already in fpath
|
|
65
|
-
*
|
|
66
|
-
* @param completionsDir - Directory to check for in fpath
|
|
67
|
-
* @returns true if configuration is needed, false if directory is already referenced
|
|
68
|
-
*/
|
|
69
|
-
private needsFpathConfig;
|
|
70
62
|
/**
|
|
71
63
|
* Remove .zshrc configuration
|
|
72
64
|
* Used during uninstallation
|
|
@@ -148,26 +148,6 @@ export class ZshInstaller {
|
|
|
148
148
|
return false;
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
|
-
/**
|
|
152
|
-
* Check if fpath configuration is needed for a given directory
|
|
153
|
-
* Used to verify if Oh My Zsh (or other) completions directory is already in fpath
|
|
154
|
-
*
|
|
155
|
-
* @param completionsDir - Directory to check for in fpath
|
|
156
|
-
* @returns true if configuration is needed, false if directory is already referenced
|
|
157
|
-
*/
|
|
158
|
-
async needsFpathConfig(completionsDir) {
|
|
159
|
-
try {
|
|
160
|
-
const zshrcPath = this.getZshrcPath();
|
|
161
|
-
const content = await fs.readFile(zshrcPath, 'utf-8');
|
|
162
|
-
// Check if fpath already includes this directory
|
|
163
|
-
return !content.includes(completionsDir);
|
|
164
|
-
}
|
|
165
|
-
catch (error) {
|
|
166
|
-
// If we can't read .zshrc, assume config is needed
|
|
167
|
-
console.debug(`Unable to read .zshrc to check fpath config: ${error instanceof Error ? error.message : String(error)}`);
|
|
168
|
-
return true;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
151
|
/**
|
|
172
152
|
* Remove .zshrc configuration
|
|
173
153
|
* Used during uninstallation
|
|
@@ -256,18 +236,10 @@ export class ZshInstaller {
|
|
|
256
236
|
const backupPath = isUpdate ? await this.backupExistingFile(targetPath) : undefined;
|
|
257
237
|
// Write the completion script
|
|
258
238
|
await fs.writeFile(targetPath, completionScript, 'utf-8');
|
|
259
|
-
// Auto-configure .zshrc
|
|
239
|
+
// Auto-configure .zshrc for standard Zsh only.
|
|
240
|
+
// Oh My Zsh loads custom/completions and runs compinit itself.
|
|
260
241
|
let zshrcConfigured = false;
|
|
261
|
-
if (isOhMyZsh) {
|
|
262
|
-
// For Oh My Zsh, verify that custom/completions is in fpath
|
|
263
|
-
// If not, add it to .zshrc
|
|
264
|
-
const needsConfig = await this.needsFpathConfig(targetDir);
|
|
265
|
-
if (needsConfig) {
|
|
266
|
-
zshrcConfigured = await this.configureZshrc(targetDir);
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
else {
|
|
270
|
-
// Standard Zsh always needs .zshrc configuration
|
|
242
|
+
if (!isOhMyZsh) {
|
|
271
243
|
zshrcConfigured = await this.configureZshrc(targetDir);
|
|
272
244
|
}
|
|
273
245
|
// Generate instructions (only if .zshrc wasn't auto-configured)
|
|
@@ -392,7 +364,7 @@ export class ZshInstaller {
|
|
|
392
364
|
if (!scriptRemoved && !zshrcWasPresent) {
|
|
393
365
|
return {
|
|
394
366
|
success: false,
|
|
395
|
-
message:
|
|
367
|
+
message: COMPLETION_MESSAGES.zshNotInstalled,
|
|
396
368
|
};
|
|
397
369
|
}
|
|
398
370
|
const messages = [];
|
package/dist/core/config.js
CHANGED
|
@@ -23,12 +23,13 @@ export const AI_TOOLS = [
|
|
|
23
23
|
{ name: 'iFlow', value: 'iflow', available: true, successLabel: 'iFlow', skillsDir: '.iflow' },
|
|
24
24
|
{ name: 'Junie', value: 'junie', available: true, successLabel: 'Junie', skillsDir: '.junie' },
|
|
25
25
|
{ name: 'Kilo Code', value: 'kilocode', available: true, successLabel: 'Kilo Code', skillsDir: '.kilocode' },
|
|
26
|
-
{ name: 'Kimi
|
|
26
|
+
{ name: 'Kimi CLI', value: 'kimi', available: true, successLabel: 'Kimi CLI', skillsDir: '.kimi' },
|
|
27
27
|
{ name: 'Kiro', value: 'kiro', available: true, successLabel: 'Kiro', skillsDir: '.kiro' },
|
|
28
|
+
{ name: 'Lingma', value: 'lingma', available: true, successLabel: 'Lingma', skillsDir: '.lingma' },
|
|
29
|
+
{ name: 'Mistral Vibe', value: 'vibe', available: true, successLabel: 'Mistral Vibe', skillsDir: '.vibe' },
|
|
28
30
|
{ name: 'OpenCode', value: 'opencode', available: true, successLabel: 'OpenCode', skillsDir: '.opencode' },
|
|
29
31
|
{ name: 'Pi', value: 'pi', available: true, successLabel: 'Pi', skillsDir: '.pi' },
|
|
30
32
|
{ name: 'Qoder', value: 'qoder', available: true, successLabel: 'Qoder', skillsDir: '.qoder' },
|
|
31
|
-
{ name: 'Lingma', value: 'lingma', available: true, successLabel: 'Lingma', skillsDir: '.lingma' },
|
|
32
33
|
{ name: 'Qwen Code', value: 'qwen', available: true, successLabel: 'Qwen Code', skillsDir: '.qwen' },
|
|
33
34
|
{ name: 'RooCode', value: 'roocode', available: true, successLabel: 'RooCode', skillsDir: '.roo' },
|
|
34
35
|
{ name: 'Trae', value: 'trae', available: true, successLabel: 'Trae', skillsDir: '.trae' },
|
|
@@ -25,7 +25,12 @@ export declare function getGlobalConfigDir(): string;
|
|
|
25
25
|
* - Unix/macOS fallback: ~/.local/share/openspec/
|
|
26
26
|
* - Windows fallback: %LOCALAPPDATA%/openspec/
|
|
27
27
|
*/
|
|
28
|
-
export
|
|
28
|
+
export interface GlobalDataDirOptions {
|
|
29
|
+
env?: NodeJS.ProcessEnv;
|
|
30
|
+
platform?: NodeJS.Platform;
|
|
31
|
+
homedir?: string;
|
|
32
|
+
}
|
|
33
|
+
export declare function getGlobalDataDir(options?: GlobalDataDirOptions): string;
|
|
29
34
|
/**
|
|
30
35
|
* Gets the path to the global config file.
|
|
31
36
|
*/
|
|
@@ -36,32 +36,31 @@ export function getGlobalConfigDir() {
|
|
|
36
36
|
// Unix/macOS fallback: ~/.config
|
|
37
37
|
return path.join(os.homedir(), '.config', GLOBAL_CONFIG_DIR_NAME);
|
|
38
38
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
export function getGlobalDataDir() {
|
|
39
|
+
function joinGlobalDataPath(platform, ...segments) {
|
|
40
|
+
return platform === 'win32'
|
|
41
|
+
? path.win32.join(...segments)
|
|
42
|
+
: path.posix.join(...segments);
|
|
43
|
+
}
|
|
44
|
+
export function getGlobalDataDir(options = {}) {
|
|
45
|
+
const env = options.env ?? process.env;
|
|
46
|
+
const platform = options.platform ?? os.platform();
|
|
48
47
|
// XDG_DATA_HOME takes precedence on all platforms when explicitly set
|
|
49
|
-
const xdgDataHome =
|
|
48
|
+
const xdgDataHome = env.XDG_DATA_HOME;
|
|
50
49
|
if (xdgDataHome) {
|
|
51
|
-
return
|
|
50
|
+
return joinGlobalDataPath(platform, xdgDataHome, GLOBAL_DATA_DIR_NAME);
|
|
52
51
|
}
|
|
53
|
-
const
|
|
52
|
+
const homedir = options.homedir ?? os.homedir();
|
|
54
53
|
if (platform === 'win32') {
|
|
55
54
|
// Windows: use %LOCALAPPDATA%
|
|
56
|
-
const localAppData =
|
|
55
|
+
const localAppData = env.LOCALAPPDATA;
|
|
57
56
|
if (localAppData) {
|
|
58
|
-
return
|
|
57
|
+
return joinGlobalDataPath(platform, localAppData, GLOBAL_DATA_DIR_NAME);
|
|
59
58
|
}
|
|
60
59
|
// Fallback for Windows if LOCALAPPDATA is not set
|
|
61
|
-
return
|
|
60
|
+
return joinGlobalDataPath(platform, homedir, 'AppData', 'Local', GLOBAL_DATA_DIR_NAME);
|
|
62
61
|
}
|
|
63
62
|
// Unix/macOS fallback: ~/.local/share
|
|
64
|
-
return
|
|
63
|
+
return joinGlobalDataPath(platform, homedir, '.local', 'share', GLOBAL_DATA_DIR_NAME);
|
|
65
64
|
}
|
|
66
65
|
/**
|
|
67
66
|
* Gets the path to the global config file.
|