jarvis-ai-framework 1.0.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/AGENTS.md +416 -0
- package/LICENSE +21 -0
- package/README.md +190 -0
- package/agents/AGENTS.md +234 -0
- package/agents/README.md +309 -0
- package/agents/engineering/data/eng.data-engineer.agent.md +309 -0
- package/agents/engineering/eng.agent.md +303 -0
- package/agents/engineering/eng.bug-hunter.md +386 -0
- package/agents/engineering/eng.cybersecurity.agent.md +503 -0
- package/agents/engineering/eng.dev-code-reviewer.md +148 -0
- package/agents/engineering/eng.docs-writer.md +152 -0
- package/agents/engineering/eng.frontend.agent.md +117 -0
- package/agents/engineering/eng.rpa.agent.md +215 -0
- package/agents/engineering/eng.tech-analyst.agent.md +102 -0
- package/agents/engineering/eng.ux-designer.agent.md +193 -0
- package/agents/engineering/qa/eng.qa.cypress-specialist.md +109 -0
- package/agents/engineering/qa/eng.qa.quality-champion-task-agent.md +85 -0
- package/agents/engineering/qa/eng.qa.quality-strategist.md +111 -0
- package/agents/engineering/qa/eng.qa.test-architect.md +400 -0
- package/agents/engineering/qa/eng.qa.test-planner.md +477 -0
- package/agents/engineering/qa/eng.qa.testing-engineer.md +339 -0
- package/agents/product/prod.pm-checker.md +52 -0
- package/bin/commands/docs-publish.js +184 -0
- package/bin/commands/docs-sync.js +139 -0
- package/bin/commands/info.js +87 -0
- package/bin/commands/init.js +237 -0
- package/bin/commands/install-rtk.js +90 -0
- package/bin/commands/list.js +48 -0
- package/bin/commands/qa-signoff.js +112 -0
- package/bin/commands/whoami.js +43 -0
- package/bin/jarvis.js +159 -0
- package/bin/lib/auth/session.js +56 -0
- package/bin/lib/config/constants.js +123 -0
- package/bin/lib/config/ide-config.js +233 -0
- package/bin/lib/core/scanner.js +124 -0
- package/bin/lib/core/sync-engine.js +551 -0
- package/bin/lib/docs/fetch-file.sh +41 -0
- package/bin/lib/docs/publish-file.sh +284 -0
- package/bin/lib/docs/validate-frontmatter.js +157 -0
- package/bin/lib/env-loader.js +198 -0
- package/bin/lib/tasks/comment.js +131 -0
- package/bin/lib/utils/git-parser.js +145 -0
- package/bin/lib/utils/logger.js +104 -0
- package/bin/lib/utils/npmrc-parser.js +106 -0
- package/bin/lib/utils/paths.js +55 -0
- package/bin/lib/utils/ui.js +59 -0
- package/bin/lib/vcs/api.js +312 -0
- package/bin/lib/vcs/create-issue.js +43 -0
- package/bin/lib/vcs/create-merge.js +43 -0
- package/bin/lib/vcs/fetch-raw.js +30 -0
- package/bin/postinstall.js +41 -0
- package/members.md +25 -0
- package/package.json +55 -0
- package/rules/AGENTS.md +205 -0
- package/rules/engineering/data/data-rules.md +200 -0
- package/rules/engineering/eng-rules.md +243 -0
- package/rules/engineering/eng-security-rules.md +186 -0
- package/rules/engineering/eng.breakdown-subtasks-rules.md +585 -0
- package/rules/engineering/eng.bump-rules.md +27 -0
- package/rules/engineering/eng.docs-scraping-rules.md +64 -0
- package/rules/engineering/eng.downstream-flow-rules.md +297 -0
- package/rules/engineering/eng.integrations-rules.md +73 -0
- package/rules/engineering/eng.plan-rules.md +333 -0
- package/rules/engineering/eng.pr-rules.md +359 -0
- package/rules/engineering/eng.pre-pr-rules.md +103 -0
- package/rules/engineering/eng.start-rules.md +246 -0
- package/rules/engineering/eng.tech-spec-rules.md +968 -0
- package/rules/engineering/eng.work-rules.md +312 -0
- package/rules/engineering/frontend/eng.frontend-rules.md +147 -0
- package/rules/engineering/qa/eng.qa.cypress-standards-rules.md +259 -0
- package/rules/engineering/qa/eng.qa.exploratory-session-rules.md +137 -0
- package/rules/engineering/qa/eng.qa.quality-gate-scoring-rules.md +181 -0
- package/rules/engineering/qa/eng.qa.tech-spec-validation-criteria-rules.md +120 -0
- package/rules/engineering/rpa/eng.rpa-rules.md +230 -0
- package/rules/product/README.md +24 -0
- package/rules/product/prod-rules.md +151 -0
- package/rules/rtk-rules.md +68 -0
- package/skills/AGENTS.md +290 -0
- package/skills/SKILLS-ROADMAP.md +333 -0
- package/skills/churn-audit/SKILL.md +385 -0
- package/skills/context-detect/SKILL.md +399 -0
- package/skills/context-detect/assets/context-profile-template.md +127 -0
- package/skills/docs-central/README.md +310 -0
- package/skills/docs-central/SKILL.md +423 -0
- package/skills/docs-index/SKILL.md +377 -0
- package/skills/eng-ai-engineer/SKILL.md +296 -0
- package/skills/eng-arch-c4/SKILL.md +358 -0
- package/skills/eng-arch-c4/assets/example-code.md +189 -0
- package/skills/eng-arch-c4/assets/example-component.md +105 -0
- package/skills/eng-arch-c4/assets/example-container.md +104 -0
- package/skills/eng-arch-c4/assets/example-context.md +81 -0
- package/skills/eng-backend/SKILL.md +776 -0
- package/skills/eng-browser-extension-builder/SKILL.md +385 -0
- package/skills/eng-cybersecurity/SKILL.md +645 -0
- package/skills/eng-data-bi/SKILL.md +199 -0
- package/skills/eng-data-debug/SKILL.md +307 -0
- package/skills/eng-data-engineer/SKILL.md +256 -0
- package/skills/eng-data-onboard/SKILL.md +310 -0
- package/skills/eng-data-orchestrator/SKILL.md +426 -0
- package/skills/eng-design-system/SKILL.md +619 -0
- package/skills/eng-docs-write/SKILL.md +312 -0
- package/skills/eng-frontend/SKILL.md +913 -0
- package/skills/eng-jira-comment/SKILL.md +17 -0
- package/skills/eng-microfrontend/SKILL.md +602 -0
- package/skills/eng-ms-trace/SKILL.md +469 -0
- package/skills/eng-nestjs/SKILL.md +791 -0
- package/skills/eng-performance-engineer/SKILL.md +312 -0
- package/skills/eng-pr/SKILL.md +339 -0
- package/skills/eng-qa-a11y-audit/SKILL.md +269 -0
- package/skills/eng-qa-bug-report/SKILL.md +1088 -0
- package/skills/eng-qa-bug-report/TASK_MANAGERS.md +138 -0
- package/skills/eng-qa-cypress-e2e/SKILL.md +177 -0
- package/skills/eng-qa-dev-guide/SKILL.md +164 -0
- package/skills/eng-qa-e2e/SKILL.md +400 -0
- package/skills/eng-qa-e2e-spec-writer/SKILL.md +322 -0
- package/skills/eng-qa-exploratory/SKILL.md +188 -0
- package/skills/eng-qa-gate/SKILL.md +370 -0
- package/skills/eng-qa-gate/assets/checklist-validacao.md +291 -0
- package/skills/eng-qa-graphql-contract/SKILL.md +256 -0
- package/skills/eng-qa-quality-report/SKILL.md +412 -0
- package/skills/eng-qa-test-plan/SKILL.md +466 -0
- package/skills/eng-qa-test-plan/assets/test-coverage-template.md +92 -0
- package/skills/eng-qa-test-plan/assets/test-patterns.md +178 -0
- package/skills/eng-qa-testsprite/SKILL.md +325 -0
- package/skills/eng-qa-testsprite/references/testsprite-mcp.md +224 -0
- package/skills/eng-qa-unit-test/SKILL.md +471 -0
- package/skills/eng-rabbitmq/SKILL.md +661 -0
- package/skills/eng-scraper/SKILL.md +683 -0
- package/skills/eng-scraper-robot-builder/SKILL.md +370 -0
- package/skills/eng-security-patch/SKILL.md +378 -0
- package/skills/eng-security-triage/SKILL.md +266 -0
- package/skills/eng-task-comment/SKILL.md +60 -0
- package/skills/eng-tech-analyst/SKILL.md +529 -0
- package/skills/eng-threat-model/SKILL.md +161 -0
- package/skills/init-jarvis/SKILL.md +1304 -0
- package/skills/init-jarvis/assets/mcp-configs.md +389 -0
- package/skills/init-jarvis/assets/onboarding-checklist.md +104 -0
- package/skills/init-jarvis/assets/setup-guide.md +360 -0
- package/skills/lovable-prompt-generator/SKILL.md +304 -0
- package/skills/prod-roadmap-report/README.md +303 -0
- package/skills/prod-roadmap-report/SKILL.md +198 -0
- package/skills/prod-roadmap-report/commands/status.compiled.single.team.md +23 -0
- package/skills/prod-roadmap-report/commands/status.list.projects.md +17 -0
- package/skills/prod-roadmap-report/commands/status.memory.md +192 -0
- package/skills/prod-roadmap-report/commands/status.roadmap.preview.md +94 -0
- package/skills/prod-roadmap-report/references/detailed-guide.md +236 -0
- package/skills/prod-roadmap-report/rules/detailed-guide.md +237 -0
- package/skills/prod-roadmap-report/rules/status-report-rules.md +44 -0
- package/skills/prod-roadmap-report/templates/template-multiple-teams-compiled-status.md +53 -0
- package/skills/prod-roadmap-report/templates/template-projects-list.md +23 -0
- package/skills/prod-roadmap-report/templates/template-single-team-compiled-status.md +60 -0
- package/skills/prod-roadmap-report/templates/template-single-team-status.md +49 -0
- package/skills/prod-specs/SKILL.md +108 -0
- package/skills/prod-specs/references/prod.spec.clarify.md +176 -0
- package/skills/prod-specs/references/prod.spec.epic.md +107 -0
- package/skills/prod-specs/references/prod.spec.frd.md +135 -0
- package/skills/prod-specs/references/prod.spec.issue.md +145 -0
- package/skills/prod-specs/references/prod.spec.prd.md +118 -0
- package/skills/prod-specs/rules/prod-spec-rules.md +186 -0
- package/skills/prod-specs/templates/prod-breakdown-template.md +136 -0
- package/skills/prod-specs/templates/prod-epic-template.md +76 -0
- package/skills/prod-specs/templates/prod-frd-template.md +172 -0
- package/skills/prod-specs/templates/prod-issue-template.md +68 -0
- package/skills/prod-specs/templates/prod-prd-full-template.md +159 -0
- package/skills/prod-specs/templates/prod-prd-template.md +173 -0
- package/skills/prod-specs-update/SKILL.md +272 -0
- package/skills/report-issue/SKILL.md +156 -0
- package/taxonomy.md +270 -0
- package/templates/AGENTS.md +189 -0
- package/templates/CDD aplicado a Prompts.md +182 -0
- package/templates/ENV-template.md +187 -0
- package/templates/engineering/AGENTS-template.md +71 -0
- package/templates/engineering/ARD-template.md +193 -0
- package/templates/engineering/CONTACTS-template.md +135 -0
- package/templates/engineering/PR-template.md +40 -0
- package/templates/engineering/RFC-Playbook.md +325 -0
- package/templates/engineering/RFC-template.md +199 -0
- package/templates/engineering/architecture-template.md +277 -0
- package/templates/engineering/breakdown-subtasks-template.md +582 -0
- package/templates/engineering/c4-model-template.md +516 -0
- package/templates/engineering/data-contract-template.md +135 -0
- package/templates/engineering/data-pipeline-template.md +163 -0
- package/templates/engineering/plan-template.md +255 -0
- package/templates/engineering/qa/eng.qa.quality-gate-examples-template.md +311 -0
- package/templates/engineering/qa/eng.qa.quality-gate-report-template.md +249 -0
- package/templates/engineering/qa/qa.cypress-test-template.md +172 -0
- package/templates/engineering/qa/qa.exploratory-session-template.md +148 -0
- package/templates/engineering/qa/qa.quality-report-template.md +130 -0
- package/templates/engineering/qa/qa.release-signoff-template.md +54 -0
- package/templates/engineering/qa/qa.sprint-plan-template.md +49 -0
- package/templates/engineering/swagger-template.md +145 -0
- package/templates/engineering/tech-spec-template.md +497 -0
- package/templates/engineering/work-progress-template.md +155 -0
- package/workflows/AGENTS.md +240 -0
- package/workflows/README.md +160 -0
- package/workflows/all-tools.md +11 -0
- package/workflows/engineering/data/data.contract.md +202 -0
- package/workflows/engineering/data/data.new-pipeline.md +234 -0
- package/workflows/engineering/eng.breakdown-subtasks.md +420 -0
- package/workflows/engineering/eng.bug-audit.md +591 -0
- package/workflows/engineering/eng.build-tech-spec.md +1116 -0
- package/workflows/engineering/eng.create-ard-from-code.md +259 -0
- package/workflows/engineering/eng.create-ard.md +382 -0
- package/workflows/engineering/eng.create-rfc.md +245 -0
- package/workflows/engineering/eng.debug.md +479 -0
- package/workflows/engineering/eng.docs.md +40 -0
- package/workflows/engineering/eng.light-arch.md +84 -0
- package/workflows/engineering/eng.plan.md +213 -0
- package/workflows/engineering/eng.pr.md +466 -0
- package/workflows/engineering/eng.pre-pr.md +167 -0
- package/workflows/engineering/eng.review.md +185 -0
- package/workflows/engineering/eng.rpa.robot.md +342 -0
- package/workflows/engineering/eng.security-audit.md +312 -0
- package/workflows/engineering/eng.security-incident.md +275 -0
- package/workflows/engineering/eng.security-pipeline.md +210 -0
- package/workflows/engineering/eng.security-review.md +235 -0
- package/workflows/engineering/eng.start.md +494 -0
- package/workflows/engineering/eng.work.md +558 -0
- package/workflows/engineering/frontend/eng.frontend-component.md +190 -0
- package/workflows/engineering/frontend/eng.frontend-perf-audit.md +375 -0
- package/workflows/engineering/frontend/eng.frontend-review.md +185 -0
- package/workflows/engineering/qa/eng.qa-dev-quality-guide.md +51 -0
- package/workflows/engineering/qa/eng.qa-e2e-test-generation.md +51 -0
- package/workflows/engineering/qa/eng.qa-exploratory-session.md +60 -0
- package/workflows/engineering/qa/eng.qa-quality-gate-validation.md +202 -0
- package/workflows/engineering/qa/eng.qa-quality-report.md +83 -0
- package/workflows/engineering/qa/eng.qa-refinement-entry.md +83 -0
- package/workflows/engineering/qa/eng.qa-release-signoff.md +170 -0
- package/workflows/engineering/qa/eng.qa-sprint-planning.md +100 -0
- package/workflows/engineering/ta/eng.ta.atendimento.md +93 -0
- package/workflows/product/prod.roadmap.preview.md +110 -0
- package/workflows/product/prod.spec.breakdown.md +163 -0
- package/workflows/product/prod.spec.clarify.md +178 -0
- package/workflows/product/prod.spec.epic.md +154 -0
- package/workflows/product/prod.spec.frd.md +96 -0
- package/workflows/product/prod.spec.issue.md +145 -0
- package/workflows/product/prod.spec.md +60 -0
- package/workflows/product/prod.spec.prd.md +100 -0
- package/workflows/taxonomy.md +92 -0
- package/workflows/warm-up.md +574 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Publica documento no central-docs (GitLab / GitHub / Bitbucket) via branch + MR/PR
|
|
3
|
+
|
|
4
|
+
set -e
|
|
5
|
+
|
|
6
|
+
LOCAL_FILE="$1" # Ex: ./docs/engineering/ard-api-wallet.md
|
|
7
|
+
TIPO="$2" # prd|frd|ard|rfc
|
|
8
|
+
FEATURE="$3" # Ex: api-wallet-auth-jwt
|
|
9
|
+
|
|
10
|
+
# Resolver path absoluto do script (independente de cwd)
|
|
11
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
12
|
+
|
|
13
|
+
if [ -z "$LOCAL_FILE" ] || [ -z "$TIPO" ] || [ -z "$FEATURE" ]; then
|
|
14
|
+
echo "Uso: publish-file.sh <local-file> <tipo> <feature>" >&2
|
|
15
|
+
echo "Tipos válidos: prd, frd, ard, rfc, swagger, qa-report" >&2
|
|
16
|
+
exit 1
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
if [ ! -f "$LOCAL_FILE" ]; then
|
|
20
|
+
echo "Erro: Arquivo não encontrado: $LOCAL_FILE" >&2
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# Detectar IDE directory
|
|
25
|
+
if [ -d ".windsurf" ]; then
|
|
26
|
+
IDE=".windsurf"
|
|
27
|
+
elif [ -d ".claude" ]; then
|
|
28
|
+
IDE=".claude"
|
|
29
|
+
elif [ -d ".cursor" ]; then
|
|
30
|
+
IDE=".cursor"
|
|
31
|
+
elif [ -d ".vscode" ]; then
|
|
32
|
+
IDE=".vscode"
|
|
33
|
+
else
|
|
34
|
+
echo "Erro: IDE directory não encontrado (.windsurf, .claude, .cursor, .vscode)" >&2
|
|
35
|
+
exit 1
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
# 1. Ler ENV.md (se não houver override)
|
|
39
|
+
if [ -n "$SQUAD_OVERRIDE" ] && [ -n "$WORKSPACE_OVERRIDE" ]; then
|
|
40
|
+
# Usar override das variáveis de ambiente
|
|
41
|
+
SQUAD="$SQUAD_OVERRIDE"
|
|
42
|
+
WORKSPACE="$WORKSPACE_OVERRIDE"
|
|
43
|
+
else
|
|
44
|
+
# Ler do ENV.md
|
|
45
|
+
if [ ! -f "$IDE/ENV.md" ]; then
|
|
46
|
+
echo "Erro: ENV.md não encontrado em $IDE/" >&2
|
|
47
|
+
echo "Ou passe --squad e --workspace via CLI" >&2
|
|
48
|
+
exit 1
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
SQUAD=$(grep '^SQUAD=' "$IDE/ENV.md" | cut -d'=' -f2 | tr -d ' ')
|
|
52
|
+
WORKSPACE=$(grep '^WORKSPACE=' "$IDE/ENV.md" | cut -d'=' -f2 | tr -d ' ')
|
|
53
|
+
|
|
54
|
+
if [ -z "$SQUAD" ] || [ -z "$WORKSPACE" ]; then
|
|
55
|
+
echo "Erro: SQUAD ou WORKSPACE não definidos no ENV.md" >&2
|
|
56
|
+
echo "Ou passe --squad e --workspace via CLI" >&2
|
|
57
|
+
exit 1
|
|
58
|
+
fi
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
TARGET_BRANCH="${CENTRAL_DOCS_TARGET_BRANCH:-dev}"
|
|
62
|
+
|
|
63
|
+
if [ -z "$CENTRAL_DOCS_REPO" ]; then
|
|
64
|
+
echo "Erro: CENTRAL_DOCS_REPO não definido no ENV.md" >&2
|
|
65
|
+
exit 1
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
# 2. Validar frontmatter
|
|
69
|
+
echo "Validando frontmatter..."
|
|
70
|
+
node --input-type=module -e "
|
|
71
|
+
import { validateFrontmatter } from 'file://${SCRIPT_DIR}/validate-frontmatter.js';
|
|
72
|
+
try {
|
|
73
|
+
validateFrontmatter('$LOCAL_FILE', '$TIPO');
|
|
74
|
+
console.log('✅ Frontmatter válido');
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.error(error.message);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
"
|
|
80
|
+
|
|
81
|
+
if [ $? -ne 0 ]; then
|
|
82
|
+
exit 1
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
# 3. Extrair metadados
|
|
86
|
+
METADATA=$(node --input-type=module -e "
|
|
87
|
+
import { extractMetadata } from 'file://${SCRIPT_DIR}/validate-frontmatter.js';
|
|
88
|
+
const metadata = extractMetadata('$LOCAL_FILE');
|
|
89
|
+
console.log(JSON.stringify(metadata));
|
|
90
|
+
")
|
|
91
|
+
|
|
92
|
+
VERSION=$(echo "$METADATA" | jq -r '.version // "1.0.0"')
|
|
93
|
+
STATUS=$(echo "$METADATA" | jq -r '.status // "draft"')
|
|
94
|
+
JIRA=$(echo "$METADATA" | jq -r '.jira // ""')
|
|
95
|
+
DOC_NAME=$(echo "$METADATA" | jq -r '.name // ""')
|
|
96
|
+
|
|
97
|
+
# 4. Definir paths
|
|
98
|
+
if [ "$TIPO" = "prd" ] || [ "$TIPO" = "frd" ]; then
|
|
99
|
+
DEST_PATH="${SQUAD}/${WORKSPACE}/product/${TIPO}-${FEATURE}.md"
|
|
100
|
+
AREA="product"
|
|
101
|
+
elif [ "$TIPO" = "ard" ]; then
|
|
102
|
+
DEST_PATH="${SQUAD}/${WORKSPACE}/engineering/ARD/${TIPO}-${FEATURE}.md"
|
|
103
|
+
AREA="engineering"
|
|
104
|
+
elif [ "$TIPO" = "rfc" ]; then
|
|
105
|
+
DEST_PATH="${SQUAD}/${WORKSPACE}/engineering/RFC/${TIPO}-${FEATURE}.md"
|
|
106
|
+
AREA="engineering"
|
|
107
|
+
elif [ "$TIPO" = "swagger" ]; then
|
|
108
|
+
# Detectar extensão do arquivo (yaml, json ou md)
|
|
109
|
+
FILE_EXT="${LOCAL_FILE##*.}"
|
|
110
|
+
if [ "$FILE_EXT" = "yaml" ] || [ "$FILE_EXT" = "yml" ]; then
|
|
111
|
+
DEST_PATH="${SQUAD}/${WORKSPACE}/engineering/swagger/api-${FEATURE}.yaml"
|
|
112
|
+
elif [ "$FILE_EXT" = "json" ]; then
|
|
113
|
+
DEST_PATH="${SQUAD}/${WORKSPACE}/engineering/swagger/api-${FEATURE}.json"
|
|
114
|
+
else
|
|
115
|
+
# Default para .md (wrapper com metadados)
|
|
116
|
+
DEST_PATH="${SQUAD}/${WORKSPACE}/engineering/swagger/swagger-${FEATURE}.md"
|
|
117
|
+
fi
|
|
118
|
+
AREA="engineering"
|
|
119
|
+
elif [ "$TIPO" = "qa-report" ]; then
|
|
120
|
+
DEST_PATH="${SQUAD}/${WORKSPACE}/engineering/qa/qa-report-${FEATURE}.md"
|
|
121
|
+
AREA="engineering"
|
|
122
|
+
else
|
|
123
|
+
echo "Erro: Tipo inválido: $TIPO" >&2
|
|
124
|
+
echo "Tipos válidos: prd, frd, ard, rfc, swagger, qa-report" >&2
|
|
125
|
+
exit 1
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
INDEX_PATH="${SQUAD}/${WORKSPACE}/index.md"
|
|
129
|
+
BRANCH_NAME="docs/${SQUAD}/${WORKSPACE}/${TIPO}-${FEATURE}"
|
|
130
|
+
|
|
131
|
+
echo ""
|
|
132
|
+
echo "📋 Resumo da Publicação:"
|
|
133
|
+
echo " Squad: $SQUAD"
|
|
134
|
+
echo " Workspace: $WORKSPACE"
|
|
135
|
+
echo " Tipo: $TIPO"
|
|
136
|
+
echo " Feature: $FEATURE"
|
|
137
|
+
echo " Destino: $DEST_PATH"
|
|
138
|
+
echo " Branch: $BRANCH_NAME → $TARGET_BRANCH"
|
|
139
|
+
echo ""
|
|
140
|
+
|
|
141
|
+
# 5–6. Token e parse do repo ficam no adapter VCS (api.js)
|
|
142
|
+
|
|
143
|
+
# 7. Ler conteúdo do arquivo local e encodar em base64
|
|
144
|
+
FILE_CONTENT=$(cat "$LOCAL_FILE" | base64)
|
|
145
|
+
|
|
146
|
+
# 8. Buscar index.md atual
|
|
147
|
+
echo "Buscando index.md do workspace..."
|
|
148
|
+
INDEX_CONTENT=$(bash "${SCRIPT_DIR}/fetch-file.sh" "$INDEX_PATH" "$TARGET_BRANCH" 2>/dev/null || echo "")
|
|
149
|
+
|
|
150
|
+
if [ -z "$INDEX_CONTENT" ]; then
|
|
151
|
+
echo "⚠️ index.md não encontrado, será criado"
|
|
152
|
+
INDEX_ACTION="create"
|
|
153
|
+
INDEX_CONTENT="# $WORKSPACE
|
|
154
|
+
|
|
155
|
+
**Squad:** $SQUAD
|
|
156
|
+
|
|
157
|
+
## $AREA
|
|
158
|
+
|
|
159
|
+
"
|
|
160
|
+
else
|
|
161
|
+
INDEX_ACTION="update"
|
|
162
|
+
fi
|
|
163
|
+
|
|
164
|
+
# 9. Adicionar nova linha ao index.md
|
|
165
|
+
NEW_LINE="- [${TIPO}-${FEATURE}.md](${AREA}/${TIPO}-${FEATURE}.md) | v${VERSION} | ${STATUS}"
|
|
166
|
+
[ -n "$JIRA" ] && NEW_LINE="${NEW_LINE} | jira: ${JIRA}"
|
|
167
|
+
[ -n "$DOC_NAME" ] && NEW_LINE="${NEW_LINE} | ${DOC_NAME}"
|
|
168
|
+
|
|
169
|
+
# Adicionar linha na seção correta
|
|
170
|
+
UPDATED_INDEX=$(echo "$INDEX_CONTENT" | awk -v line="$NEW_LINE" -v area="$AREA" '
|
|
171
|
+
/^## (product|engineering)/ {
|
|
172
|
+
print
|
|
173
|
+
if ($2 == area) {
|
|
174
|
+
found = 1
|
|
175
|
+
getline
|
|
176
|
+
print line
|
|
177
|
+
}
|
|
178
|
+
next
|
|
179
|
+
}
|
|
180
|
+
{ print }
|
|
181
|
+
END {
|
|
182
|
+
if (!found) {
|
|
183
|
+
print ""
|
|
184
|
+
print "## " area
|
|
185
|
+
print line
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
')
|
|
189
|
+
|
|
190
|
+
UPDATED_INDEX_B64=$(echo "$UPDATED_INDEX" | base64)
|
|
191
|
+
|
|
192
|
+
# 10. Criar commit via adapter VCS
|
|
193
|
+
echo "Criando branch e commit..."
|
|
194
|
+
|
|
195
|
+
COMMIT_JSON=$(mktemp)
|
|
196
|
+
cat > "$COMMIT_JSON" <<EOF
|
|
197
|
+
{
|
|
198
|
+
"branch": $(printf '%s' "$BRANCH_NAME" | jq -Rs .),
|
|
199
|
+
"startBranch": $(printf '%s' "$TARGET_BRANCH" | jq -Rs .),
|
|
200
|
+
"message": $(printf '%s' "docs(${SQUAD}): add ${TIPO} ${FEATURE}" | jq -Rs .),
|
|
201
|
+
"files": [
|
|
202
|
+
{
|
|
203
|
+
"path": $(printf '%s' "$DEST_PATH" | jq -Rs .),
|
|
204
|
+
"contentBase64": $(printf '%s' "$FILE_CONTENT" | jq -Rs .),
|
|
205
|
+
"action": "create"
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
"path": $(printf '%s' "$INDEX_PATH" | jq -Rs .),
|
|
209
|
+
"contentBase64": $(printf '%s' "$UPDATED_INDEX_B64" | jq -Rs .),
|
|
210
|
+
"action": $(printf '%s' "$INDEX_ACTION" | jq -Rs .)
|
|
211
|
+
}
|
|
212
|
+
]
|
|
213
|
+
}
|
|
214
|
+
EOF
|
|
215
|
+
|
|
216
|
+
COMMIT_RESULT=$(node --input-type=module -e "
|
|
217
|
+
import { readFileSync } from 'node:fs';
|
|
218
|
+
import { commitFiles } from 'file://${SCRIPT_DIR}/../vcs/api.js';
|
|
219
|
+
const opts = JSON.parse(readFileSync('$COMMIT_JSON', 'utf-8'));
|
|
220
|
+
const result = await commitFiles(process.env.CENTRAL_DOCS_REPO, opts);
|
|
221
|
+
console.log(JSON.stringify(result));
|
|
222
|
+
" 2>&1)
|
|
223
|
+
COMMIT_EXIT=$?
|
|
224
|
+
rm -f "$COMMIT_JSON"
|
|
225
|
+
|
|
226
|
+
if [ $COMMIT_EXIT -ne 0 ]; then
|
|
227
|
+
echo "❌ Erro ao criar commit:" >&2
|
|
228
|
+
echo "$COMMIT_RESULT" >&2
|
|
229
|
+
exit 1
|
|
230
|
+
fi
|
|
231
|
+
|
|
232
|
+
COMMIT_SHA=$(echo "$COMMIT_RESULT" | jq -r '.sha')
|
|
233
|
+
echo "✅ Commit criado: $COMMIT_SHA"
|
|
234
|
+
|
|
235
|
+
# 11. Criar MR/PR
|
|
236
|
+
echo "Criando Merge/Pull Request..."
|
|
237
|
+
|
|
238
|
+
MR_DESCRIPTION="## Documento
|
|
239
|
+
|
|
240
|
+
- **Tipo**: ${TIPO}
|
|
241
|
+
- **Workspace**: ${WORKSPACE}
|
|
242
|
+
- **Feature**: ${FEATURE}"
|
|
243
|
+
|
|
244
|
+
[ -n "$JIRA" ] && MR_DESCRIPTION="${MR_DESCRIPTION}
|
|
245
|
+
- **Card**: ${JIRA}"
|
|
246
|
+
|
|
247
|
+
[ -n "$DOC_NAME" ] && MR_DESCRIPTION="${MR_DESCRIPTION}
|
|
248
|
+
- **Nome**: ${DOC_NAME}"
|
|
249
|
+
|
|
250
|
+
BODY_FILE=$(mktemp)
|
|
251
|
+
printf '%s' "$MR_DESCRIPTION" > "$BODY_FILE"
|
|
252
|
+
|
|
253
|
+
MR_RESULT=$(node "${SCRIPT_DIR}/../vcs/create-merge.js" \
|
|
254
|
+
--repo "$CENTRAL_DOCS_REPO" \
|
|
255
|
+
--source "$BRANCH_NAME" \
|
|
256
|
+
--target "$TARGET_BRANCH" \
|
|
257
|
+
--title "docs(${SQUAD}): ${TIPO} ${FEATURE}" \
|
|
258
|
+
--body-file "$BODY_FILE" 2>&1)
|
|
259
|
+
MR_EXIT=$?
|
|
260
|
+
rm -f "$BODY_FILE"
|
|
261
|
+
|
|
262
|
+
if [ $MR_EXIT -ne 0 ]; then
|
|
263
|
+
echo "❌ Erro ao criar MR/PR:" >&2
|
|
264
|
+
echo "$MR_RESULT" >&2
|
|
265
|
+
exit 1
|
|
266
|
+
fi
|
|
267
|
+
|
|
268
|
+
MR_URL=$(echo "$MR_RESULT" | tail -1 | jq -r '.url // empty')
|
|
269
|
+
MR_IID=$(echo "$MR_RESULT" | tail -1 | jq -r '.iid // empty')
|
|
270
|
+
|
|
271
|
+
echo ""
|
|
272
|
+
echo "✅ Merge/Pull Request criado com sucesso!"
|
|
273
|
+
echo ""
|
|
274
|
+
echo " #${MR_IID}: ${BRANCH_NAME} → ${TARGET_BRANCH}"
|
|
275
|
+
echo " URL: ${MR_URL}"
|
|
276
|
+
echo ""
|
|
277
|
+
|
|
278
|
+
# 12. Invalidar cache Redis
|
|
279
|
+
redis-cli DEL "docs:${INDEX_PATH}:${TARGET_BRANCH}" >/dev/null 2>&1 || true
|
|
280
|
+
redis-cli DEL "docs:${INDEX_PATH}:main" >/dev/null 2>&1 || true
|
|
281
|
+
|
|
282
|
+
echo "🔄 Cache Redis invalidado"
|
|
283
|
+
echo ""
|
|
284
|
+
echo "✅ Publicação concluída!"
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Valida frontmatter YAML de documentos (PRD, FRD, ARD, RFC)
|
|
5
|
+
*
|
|
6
|
+
* @param {string} filePath - Caminho do arquivo
|
|
7
|
+
* @param {string} tipo - Tipo do documento (prd|frd|ard|rfc)
|
|
8
|
+
* @returns {Object} Metadados extraídos
|
|
9
|
+
* @throws {Error} Se frontmatter for inválido
|
|
10
|
+
*/
|
|
11
|
+
export function validateFrontmatter(filePath, tipo) {
|
|
12
|
+
const content = readFileSync(filePath, 'utf-8')
|
|
13
|
+
|
|
14
|
+
// Extrair frontmatter YAML (entre --- e ---)
|
|
15
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/)
|
|
16
|
+
|
|
17
|
+
if (!frontmatterMatch) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
`Frontmatter YAML não encontrado em ${filePath}\n` +
|
|
20
|
+
'Formato esperado:\n' +
|
|
21
|
+
'---\n' +
|
|
22
|
+
'id: DOC-001\n' +
|
|
23
|
+
'version: 1.0.0\n' +
|
|
24
|
+
'---'
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const frontmatter = frontmatterMatch[1]
|
|
29
|
+
const metadata = parseFrontmatter(frontmatter)
|
|
30
|
+
|
|
31
|
+
// Validar campos obrigatórios por tipo
|
|
32
|
+
const requiredFields = getRequiredFields(tipo)
|
|
33
|
+
const missingFields = []
|
|
34
|
+
|
|
35
|
+
for (const field of requiredFields) {
|
|
36
|
+
if (!metadata[field]) {
|
|
37
|
+
missingFields.push(field)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (missingFields.length > 0) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`Campos obrigatórios ausentes no frontmatter de ${tipo.toUpperCase()}:\n` +
|
|
44
|
+
missingFields.map(f => ` - ${f}`).join('\n') +
|
|
45
|
+
'\n\nFrontmatter atual:\n' + frontmatter
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Validar formato de version (X.Y.Z)
|
|
50
|
+
if (metadata.version && !metadata.version.match(/^\d+\.\d+(\.\d+)?$/)) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`Formato de version inválido: "${metadata.version}"\n` +
|
|
53
|
+
'Formato esperado: X.Y.Z (ex: 1.0.0 ou 1.2)'
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Validar status
|
|
58
|
+
const validStatuses = ['icebox', 'in_review', 'in_progress', 'in_production', 'deprecated', 'Proposta', 'Em desenvolvimento', 'Em validação', 'Finalizada', 'Draft', 'In Review', 'Ready for Decision', 'Accepted', 'Rejected', 'Superseded']
|
|
59
|
+
|
|
60
|
+
if (metadata.status && !validStatuses.includes(metadata.status)) {
|
|
61
|
+
console.warn(
|
|
62
|
+
`⚠️ Status "${metadata.status}" não está na lista padrão.\n` +
|
|
63
|
+
`Status válidos: ${validStatuses.join(', ')}`
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return metadata
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Parseia frontmatter YAML simples (key: value)
|
|
72
|
+
*
|
|
73
|
+
* @param {string} frontmatter - Conteúdo do frontmatter
|
|
74
|
+
* @returns {Object} Metadados parseados
|
|
75
|
+
*/
|
|
76
|
+
function parseFrontmatter(frontmatter) {
|
|
77
|
+
const metadata = {}
|
|
78
|
+
const lines = frontmatter.split('\n')
|
|
79
|
+
|
|
80
|
+
for (const line of lines) {
|
|
81
|
+
const trimmed = line.trim()
|
|
82
|
+
if (!trimmed || trimmed.startsWith('#')) continue
|
|
83
|
+
|
|
84
|
+
const colonIndex = trimmed.indexOf(':')
|
|
85
|
+
if (colonIndex === -1) continue
|
|
86
|
+
|
|
87
|
+
const key = trimmed.slice(0, colonIndex).trim()
|
|
88
|
+
const value = trimmed.slice(colonIndex + 1).trim()
|
|
89
|
+
|
|
90
|
+
if (key && value) {
|
|
91
|
+
metadata[key] = value
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return metadata
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Retorna campos obrigatórios por tipo de documento
|
|
100
|
+
*
|
|
101
|
+
* @param {string} tipo - Tipo do documento
|
|
102
|
+
* @returns {string[]} Lista de campos obrigatórios
|
|
103
|
+
*/
|
|
104
|
+
function getRequiredFields(tipo) {
|
|
105
|
+
const fields = {
|
|
106
|
+
prd: ['id', 'name', 'version', 'status'],
|
|
107
|
+
frd: ['id', 'name', 'version', 'status', 'related_prd'],
|
|
108
|
+
ard: ['Status', 'Data', 'Autor', 'Versão'],
|
|
109
|
+
rfc: ['Status', 'Criado em', 'Proponente'],
|
|
110
|
+
'qa-report': ['period', 'squad', 'version']
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return fields[tipo] || []
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Extrai metadados específicos do frontmatter
|
|
118
|
+
*
|
|
119
|
+
* @param {string} filePath - Caminho do arquivo
|
|
120
|
+
* @returns {Object} Metadados extraídos (id, version, status, jira, etc)
|
|
121
|
+
*/
|
|
122
|
+
export function extractMetadata(filePath) {
|
|
123
|
+
const content = readFileSync(filePath, 'utf-8')
|
|
124
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/)
|
|
125
|
+
|
|
126
|
+
if (!frontmatterMatch) {
|
|
127
|
+
return {}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const metadata = parseFrontmatter(frontmatterMatch[1])
|
|
131
|
+
|
|
132
|
+
// Normalizar campos (suportar variações de nomenclatura)
|
|
133
|
+
const taskLink = metadata.task_link || metadata.jira
|
|
134
|
+
const jiraId = taskLink ? extractJiraFromContent(taskLink) : extractJiraFromContent(content)
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
id: metadata.id || metadata.ID,
|
|
138
|
+
name: metadata.name || metadata.Name,
|
|
139
|
+
version: metadata.version || metadata.Version || metadata.Versão,
|
|
140
|
+
status: metadata.status || metadata.Status,
|
|
141
|
+
jira: jiraId,
|
|
142
|
+
related_prd: metadata.related_prd,
|
|
143
|
+
author: metadata.author || metadata.Autor || metadata.created_by,
|
|
144
|
+
date: metadata.date || metadata.Data || metadata.created_at
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Extrai Jira ID do conteúdo (fallback se não estiver no frontmatter)
|
|
150
|
+
*
|
|
151
|
+
* @param {string} content - Conteúdo do arquivo
|
|
152
|
+
* @returns {string|null} Jira ID ou null
|
|
153
|
+
*/
|
|
154
|
+
function extractJiraFromContent(content) {
|
|
155
|
+
const match = content.match(/\b([A-Z]+-\d+)\b/)
|
|
156
|
+
return match ? match[1] : null
|
|
157
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
import { join, isAbsolute, dirname, basename } from "node:path";
|
|
4
|
+
|
|
5
|
+
const IDE_DIRS = {
|
|
6
|
+
windsurf: ".windsurf",
|
|
7
|
+
claude: ".claude",
|
|
8
|
+
cursor: ".cursor",
|
|
9
|
+
vscode: ".vscode",
|
|
10
|
+
codex: ".codex",
|
|
11
|
+
opencode: ".opencode",
|
|
12
|
+
gemini: ".gemini",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const AUTO_DETECT_ORDER = [
|
|
16
|
+
"windsurf",
|
|
17
|
+
"claude",
|
|
18
|
+
"cursor",
|
|
19
|
+
"vscode",
|
|
20
|
+
"codex",
|
|
21
|
+
"opencode",
|
|
22
|
+
"gemini",
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
function* walkAncestors(start) {
|
|
26
|
+
let dir = start;
|
|
27
|
+
while (true) {
|
|
28
|
+
yield dir;
|
|
29
|
+
const parent = dirname(dir);
|
|
30
|
+
if (parent === dir) break;
|
|
31
|
+
dir = parent;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function envsAt(dir, ideFilter = null) {
|
|
36
|
+
const ides = ideFilter ? [ideFilter] : AUTO_DETECT_ORDER;
|
|
37
|
+
const found = [];
|
|
38
|
+
for (const ide of ides) {
|
|
39
|
+
const folder = IDE_DIRS[ide];
|
|
40
|
+
if (!folder) continue;
|
|
41
|
+
const p = join(dir, folder, "ENV.md");
|
|
42
|
+
if (existsSync(p)) found.push({ ide, path: p, root: dir });
|
|
43
|
+
}
|
|
44
|
+
return found;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Nome do workspace: pasta que contém $IDE/ (não o repo aberto). */
|
|
48
|
+
export function deriveWorkspaceName(envPath) {
|
|
49
|
+
const parent = dirname(envPath);
|
|
50
|
+
const name = basename(parent);
|
|
51
|
+
if (name.startsWith(".")) return basename(dirname(parent));
|
|
52
|
+
return name;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Resolve o path do ENV.md com a seguinte precedência:
|
|
57
|
+
* 1. --env-file (path explícito)
|
|
58
|
+
* 2. --ide (ex: windsurf → .windsurf/ENV.md) — sobe pastas se estiver num repo filho
|
|
59
|
+
* 3. JARVIS_ENV_FILE (variável de ambiente)
|
|
60
|
+
* 4. IDE (variável de ambiente → .{ide}/ENV.md)
|
|
61
|
+
* 5. Auto-detect (sobe até achar $IDE/ENV.md no workspace)
|
|
62
|
+
*
|
|
63
|
+
* Retorna { path, source, warnings, root }
|
|
64
|
+
*/
|
|
65
|
+
export function resolveEnvPath(cwd, flags = {}) {
|
|
66
|
+
// 1. --env-file
|
|
67
|
+
if (flags["env-file"]) {
|
|
68
|
+
const p = isAbsolute(flags["env-file"])
|
|
69
|
+
? flags["env-file"]
|
|
70
|
+
: join(cwd, flags["env-file"]);
|
|
71
|
+
if (!existsSync(p)) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`ENV.md não encontrado: ${p}\n` +
|
|
74
|
+
`Dica: verifique o path passado em --env-file`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
return { path: p, source: "--env-file", warnings: [], root: dirname(dirname(p)) };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 2. --ide (cwd e ancestrais — abre um repo filho e ainda acha o workspace)
|
|
81
|
+
if (flags.ide) {
|
|
82
|
+
if (!IDE_DIRS[flags.ide]) {
|
|
83
|
+
throw new Error(
|
|
84
|
+
`IDE não suportada: "${flags.ide}"\n` +
|
|
85
|
+
`IDEs válidas: ${Object.keys(IDE_DIRS).join(", ")}`,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
for (const dir of walkAncestors(cwd)) {
|
|
89
|
+
const found = envsAt(dir, flags.ide);
|
|
90
|
+
if (found.length === 1) {
|
|
91
|
+
return {
|
|
92
|
+
path: found[0].path,
|
|
93
|
+
source: `--ide ${flags.ide}`,
|
|
94
|
+
warnings: [],
|
|
95
|
+
root: found[0].root,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
throw new Error(
|
|
100
|
+
`ENV.md não encontrado para --ide ${flags.ide} a partir de ${cwd}\n` +
|
|
101
|
+
`Dica: rode jarvis init --ide ${flags.ide} na pasta do workspace`,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 3. JARVIS_ENV_FILE
|
|
106
|
+
if (process.env.JARVIS_ENV_FILE) {
|
|
107
|
+
const p = isAbsolute(process.env.JARVIS_ENV_FILE)
|
|
108
|
+
? process.env.JARVIS_ENV_FILE
|
|
109
|
+
: join(cwd, process.env.JARVIS_ENV_FILE);
|
|
110
|
+
if (!existsSync(p)) {
|
|
111
|
+
throw new Error(`ENV.md não encontrado (JARVIS_ENV_FILE): ${p}`);
|
|
112
|
+
}
|
|
113
|
+
return { path: p, source: "JARVIS_ENV_FILE", warnings: [], root: dirname(dirname(p)) };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 4. IDE env var
|
|
117
|
+
if (process.env.IDE && IDE_DIRS[process.env.IDE]) {
|
|
118
|
+
for (const dir of walkAncestors(cwd)) {
|
|
119
|
+
const found = envsAt(dir, process.env.IDE);
|
|
120
|
+
if (found.length === 1) {
|
|
121
|
+
return {
|
|
122
|
+
path: found[0].path,
|
|
123
|
+
source: `IDE=${process.env.IDE}`,
|
|
124
|
+
warnings: [],
|
|
125
|
+
root: found[0].root,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 5. Auto-detect (ancestral mais próximo com exatamente um ENV.md)
|
|
132
|
+
for (const dir of walkAncestors(cwd)) {
|
|
133
|
+
const found = envsAt(dir);
|
|
134
|
+
if (found.length > 1) {
|
|
135
|
+
const ides = found.map((f) => f.ide).join(" | ");
|
|
136
|
+
throw new Error(
|
|
137
|
+
`Múltiplos ENV.md encontrados em ${dir} (${ides}).\n` +
|
|
138
|
+
`Especifique a IDE ativa. Exemplo: --ide claude`,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
if (found.length === 1) {
|
|
142
|
+
return {
|
|
143
|
+
path: found[0].path,
|
|
144
|
+
source: `auto-detect (${found[0].ide})`,
|
|
145
|
+
warnings: [],
|
|
146
|
+
root: found[0].root,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Fallback: ENV.md na raiz (comportamento legado)
|
|
152
|
+
return {
|
|
153
|
+
path: join(cwd, "ENV.md"),
|
|
154
|
+
source: "auto-detect (root)",
|
|
155
|
+
warnings: [],
|
|
156
|
+
root: cwd,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Lê e parseia o ENV.md do projeto.
|
|
162
|
+
* @param {string} cwd - Diretório raiz do projeto
|
|
163
|
+
* @param {string|null} envPath - Path explícito do ENV.md (opcional)
|
|
164
|
+
* Retorna um objeto com todas as variáveis key=value encontradas.
|
|
165
|
+
*/
|
|
166
|
+
export function loadEnv(cwd, envPath = null) {
|
|
167
|
+
const p = envPath ?? join(cwd, "ENV.md");
|
|
168
|
+
if (!existsSync(p)) return {};
|
|
169
|
+
|
|
170
|
+
const lines = readFileSync(p, "utf-8").split("\n");
|
|
171
|
+
const env = {};
|
|
172
|
+
|
|
173
|
+
for (const line of lines) {
|
|
174
|
+
const trimmed = line.trim();
|
|
175
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
176
|
+
const idx = trimmed.indexOf("=");
|
|
177
|
+
if (idx === -1) continue;
|
|
178
|
+
const key = trimmed.slice(0, idx).trim();
|
|
179
|
+
const value = trimmed.slice(idx + 1).trim();
|
|
180
|
+
if (key && value) env[key] = value;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (!env.WORKSPACE) {
|
|
184
|
+
env.WORKSPACE = deriveWorkspaceName(p);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return env;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** `{workspace}/.jarvis` — sobe ancestrais até achar `$IDE/ENV.md`. */
|
|
191
|
+
export function getJarvisDir(cwd = process.cwd(), flags = {}) {
|
|
192
|
+
try {
|
|
193
|
+
const { root } = resolveEnvPath(cwd, flags);
|
|
194
|
+
return join(root || cwd, ".jarvis");
|
|
195
|
+
} catch {
|
|
196
|
+
return join(cwd, ".jarvis");
|
|
197
|
+
}
|
|
198
|
+
}
|