create-harness-btp 0.2.5 → 0.3.1
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/AI/VERSION +1 -1
- package/AI/agents/analyst.md +30 -0
- package/AI/agents/architect.md +29 -0
- package/AI/agents/backend-cap.md +19 -0
- package/AI/agents/deploy-cf.md +26 -0
- package/AI/agents/documentarian.md +20 -0
- package/AI/agents/e2e-fiori.md +62 -0
- package/AI/agents/frontend-ui5.md +20 -0
- package/AI/agents/harness-evolution.md +38 -0
- package/AI/agents/orchestrator.md +41 -0
- package/AI/agents/qa-review.md +28 -0
- package/AI/agents/researcher.md +29 -0
- package/AI/clients/claude/agents/analyst.md +22 -0
- package/AI/clients/claude/agents/architect.md +21 -0
- package/AI/clients/claude/agents/backend-cap.md +18 -0
- package/AI/clients/claude/agents/deploy-cf.md +25 -0
- package/AI/clients/claude/agents/documentarian.md +15 -0
- package/AI/clients/claude/agents/e2e-fiori.md +61 -0
- package/AI/clients/claude/agents/frontend-ui5.md +19 -0
- package/AI/clients/claude/agents/harness-evolution.md +37 -0
- package/AI/clients/claude/agents/orchestrator.md +40 -0
- package/AI/clients/claude/agents/qa-review.md +27 -0
- package/AI/clients/claude/agents/researcher.md +21 -0
- package/AI/clients/claude/commands/flow.md +16 -0
- package/AI/clients/claude/commands/verify.md +18 -0
- package/AI/clients/claude/skills/cap-cli/SKILL.md +21 -0
- package/AI/clients/claude/skills/cf-cli/SKILL.md +41 -0
- package/AI/clients/claude/skills/mta-build/SKILL.md +45 -0
- package/AI/clients/claude/skills/sap-verify/SKILL.md +27 -0
- package/AI/clients/copilot/agents/analyst.agent.md +22 -0
- package/AI/clients/copilot/agents/architect.agent.md +21 -0
- package/AI/clients/copilot/agents/backend-cap.agent.md +18 -0
- package/AI/clients/copilot/agents/deploy-cf.agent.md +25 -0
- package/AI/clients/copilot/agents/documentarian.agent.md +15 -0
- package/AI/clients/copilot/agents/e2e-fiori.agent.md +61 -0
- package/AI/clients/copilot/agents/frontend-ui5.agent.md +19 -0
- package/AI/clients/copilot/agents/harness-evolution.agent.md +37 -0
- package/AI/clients/copilot/agents/orchestrator.agent.md +40 -0
- package/AI/clients/copilot/agents/qa-review.agent.md +27 -0
- package/AI/clients/copilot/agents/researcher.agent.md +21 -0
- package/AI/clients/copilot/prompts/flow.prompt.md +16 -0
- package/AI/clients/copilot/prompts/verify.prompt.md +18 -0
- package/AI/clients/copilot/skills/cap-cli/SKILL.md +21 -0
- package/AI/clients/copilot/skills/cf-cli/SKILL.md +41 -0
- package/AI/clients/copilot/skills/mta-build/SKILL.md +45 -0
- package/AI/clients/copilot/skills/sap-verify/SKILL.md +27 -0
- package/AI/clients/cursor/commands/flow.md +12 -0
- package/AI/clients/cursor/commands/verify.md +14 -0
- package/AI/clients/cursor/skills/cap-cli/SKILL.md +21 -0
- package/AI/clients/cursor/skills/cf-cli/SKILL.md +41 -0
- package/AI/clients/cursor/skills/mta-build/SKILL.md +45 -0
- package/AI/clients/cursor/skills/sap-verify/SKILL.md +27 -0
- package/AI/clients/opencode/agents/analyst.md +29 -0
- package/AI/clients/opencode/agents/architect.md +28 -0
- package/AI/clients/opencode/agents/backend-cap.md +18 -0
- package/AI/clients/opencode/agents/deploy-cf.md +25 -0
- package/AI/clients/opencode/agents/documentarian.md +19 -0
- package/AI/clients/opencode/agents/e2e-fiori.md +61 -0
- package/AI/clients/opencode/agents/frontend-ui5.md +19 -0
- package/AI/clients/opencode/agents/harness-evolution.md +37 -0
- package/AI/clients/opencode/agents/orchestrator.md +40 -0
- package/AI/clients/opencode/agents/qa-review.md +27 -0
- package/AI/clients/opencode/agents/researcher.md +28 -0
- package/AI/clients/opencode/commands/flow.md +16 -0
- package/AI/clients/opencode/commands/verify.md +18 -0
- package/AI/clients/opencode/skills/cap-cli/SKILL.md +21 -0
- package/AI/clients/opencode/skills/cf-cli/SKILL.md +41 -0
- package/AI/clients/opencode/skills/mta-build/SKILL.md +45 -0
- package/AI/clients/opencode/skills/sap-verify/SKILL.md +27 -0
- package/AI/commands/flow.md +16 -0
- package/AI/commands/verify.md +18 -0
- package/AI/skills/cap-cli/SKILL.md +21 -0
- package/AI/skills/cf-cli/SKILL.md +41 -0
- package/AI/skills/mta-build/SKILL.md +45 -0
- package/AI/skills/sap-verify/SKILL.md +27 -0
- package/README.md +83 -17
- package/VERSION +1 -1
- package/bin/harness-btp.js +74 -28
- package/install.ps1 +72 -22
- package/install.sh +88 -28
- package/package.json +1 -1
package/bin/harness-btp.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// harness-btp init --dest <dir> [--force] [--ref <tag>]
|
|
3
|
-
// Copia AI/, crea AGENTS.md puntero, fusiona
|
|
4
|
-
// raíz (solo faltantes; con --force hace
|
|
2
|
+
// harness-btp init --dest <dir> [--force] [--ref <tag>] [--client auto|all|opencode,claude,...]
|
|
3
|
+
// Copia AI/, crea AGENTS.md puntero, fusiona el árbol del cliente agéntico
|
|
4
|
+
// desde AI/clients/<cliente>/ en la raíz (solo faltantes; con --force hace
|
|
5
|
+
// backup a <dir>.bak) y fusiona .gitignore. Sin secretos.
|
|
5
6
|
// No toca package.json ni mta.yaml del proyecto.
|
|
6
7
|
const fs = require("fs");
|
|
7
8
|
const path = require("path");
|
|
@@ -19,10 +20,11 @@ if (!cmd || cmd.startsWith("-")) {
|
|
|
19
20
|
const dest = arg("--dest", ".");
|
|
20
21
|
const force = process.argv.includes("--force");
|
|
21
22
|
const ref = arg("--ref", null);
|
|
23
|
+
const clientSpec = arg("--client", "auto");
|
|
22
24
|
const src = path.join(__dirname, "..");
|
|
23
25
|
|
|
24
26
|
if (cmd !== "init") {
|
|
25
|
-
console.log("Uso: harness-btp init --dest <dir> [--force] [--ref <tag>]");
|
|
27
|
+
console.log("Uso: harness-btp init --dest <dir> [--force] [--ref <tag>] [--client auto|all|opencode,claude,...]");
|
|
26
28
|
process.exit(1);
|
|
27
29
|
}
|
|
28
30
|
if (ref) console.log("REF: " + ref + " (el paquete incluye su propia versión; ver VERSION)");
|
|
@@ -62,43 +64,87 @@ if (fs.existsSync(agentsDst)) {
|
|
|
62
64
|
console.log("CREATE: AGENTS.md (puntero a AI/AGENTS.md)");
|
|
63
65
|
}
|
|
64
66
|
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (
|
|
72
|
-
|
|
67
|
+
const KNOWN = ["opencode", "claude", "cursor", "copilot", "aider"];
|
|
68
|
+
function resolveClients(spec, destDir) {
|
|
69
|
+
const s = (spec + "").trim().toLowerCase();
|
|
70
|
+
if (s === "auto") {
|
|
71
|
+
const d = [];
|
|
72
|
+
if (fs.existsSync(path.join(destDir, "CLAUDE.md")) || fs.existsSync(path.join(destDir, ".claude"))) d.push("claude");
|
|
73
|
+
if (fs.existsSync(path.join(destDir, ".cursor"))) d.push("cursor");
|
|
74
|
+
if (fs.existsSync(path.join(destDir, ".github"))) d.push("copilot");
|
|
75
|
+
if (fs.readdirSync(destDir, { withFileTypes: true }).some((e) => e.isFile() && e.name.startsWith("opencode.json")) || fs.existsSync(path.join(destDir, ".opencode"))) d.push("opencode");
|
|
76
|
+
if (!d.length) d.push("opencode");
|
|
77
|
+
console.log("CLIENT: auto -> " + d.join(","));
|
|
78
|
+
return d;
|
|
73
79
|
}
|
|
74
|
-
return
|
|
80
|
+
if (s === "all") return ["opencode", "claude", "cursor", "copilot"];
|
|
81
|
+
const list = s.split(",").map((x) => x.trim().toLowerCase()).filter(Boolean);
|
|
82
|
+
for (const c of list) {
|
|
83
|
+
if (!KNOWN.includes(c)) {
|
|
84
|
+
console.log("ABORT: cliente desconocido '" + c + "' (usa auto|all|opencode,claude,cursor,copilot,aider)");
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (!list.length) {
|
|
89
|
+
console.log("ABORT: --client vacío (usa auto|all|opencode,claude,cursor,copilot,aider)");
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
return list;
|
|
75
93
|
}
|
|
76
|
-
|
|
94
|
+
|
|
95
|
+
function mergeTree(treeSrc, treeDst, label) {
|
|
96
|
+
if (!fs.existsSync(treeSrc)) {
|
|
97
|
+
console.log("AVISO: no existe " + treeSrc + " (regenera con node scripts/render-clients.js)");
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const bakLabel = label.replace(/\/$/, "") + ".bak";
|
|
77
101
|
if (force) {
|
|
78
|
-
if (fs.existsSync(
|
|
79
|
-
const
|
|
80
|
-
fs.rmSync(
|
|
81
|
-
fs.renameSync(
|
|
82
|
-
console.log("BACKUP:
|
|
102
|
+
if (fs.existsSync(treeDst)) {
|
|
103
|
+
const bak = treeDst + ".bak";
|
|
104
|
+
fs.rmSync(bak, { recursive: true, force: true });
|
|
105
|
+
fs.renameSync(treeDst, bak);
|
|
106
|
+
console.log("BACKUP: " + label + " -> " + bakLabel);
|
|
83
107
|
}
|
|
84
|
-
fs.cpSync(
|
|
85
|
-
console.log("CREATE:
|
|
108
|
+
fs.cpSync(treeSrc, treeDst, { recursive: true });
|
|
109
|
+
console.log("CREATE: " + label + " (+" + listFilesRec(treeDst, treeDst).length + " archivos)");
|
|
86
110
|
} else {
|
|
87
|
-
let
|
|
88
|
-
for (const rel of listFilesRec(
|
|
89
|
-
const tgt = path.join(
|
|
111
|
+
let count = 0;
|
|
112
|
+
for (const rel of listFilesRec(treeSrc, treeSrc)) {
|
|
113
|
+
const tgt = path.join(treeDst, rel);
|
|
90
114
|
if (fs.existsSync(tgt)) {
|
|
91
|
-
console.log("SKIP:
|
|
115
|
+
console.log("SKIP: " + label + rel.split(path.sep).join("/") + " ya existe");
|
|
92
116
|
} else {
|
|
93
117
|
fs.mkdirSync(path.dirname(tgt), { recursive: true });
|
|
94
|
-
fs.copyFileSync(path.join(
|
|
95
|
-
|
|
118
|
+
fs.copyFileSync(path.join(treeSrc, rel), tgt);
|
|
119
|
+
count++;
|
|
96
120
|
}
|
|
97
121
|
}
|
|
98
|
-
console.log("CREATE:
|
|
122
|
+
console.log("CREATE: " + label + " (+" + count + " archivos)");
|
|
99
123
|
}
|
|
100
124
|
}
|
|
101
125
|
|
|
126
|
+
const clientsSrc = path.join(src, "AI", "clients");
|
|
127
|
+
for (const c of resolveClients(clientSpec, dest)) {
|
|
128
|
+
if (c === "opencode") mergeTree(path.join(clientsSrc, "opencode"), path.join(dest, ".opencode"), ".opencode/");
|
|
129
|
+
else if (c === "claude") mergeTree(path.join(clientsSrc, "claude"), path.join(dest, ".claude"), ".claude/");
|
|
130
|
+
else if (c === "cursor") {
|
|
131
|
+
mergeTree(path.join(clientsSrc, "cursor", "commands"), path.join(dest, ".cursor", "commands"), ".cursor/commands/");
|
|
132
|
+
mergeTree(path.join(clientsSrc, "cursor", "skills"), path.join(dest, ".agents", "skills"), ".agents/skills/");
|
|
133
|
+
} else if (c === "copilot") {
|
|
134
|
+
mergeTree(path.join(clientsSrc, "copilot", "agents"), path.join(dest, ".github", "agents"), ".github/agents/");
|
|
135
|
+
mergeTree(path.join(clientsSrc, "copilot", "skills"), path.join(dest, ".github", "skills"), ".github/skills/");
|
|
136
|
+
mergeTree(path.join(clientsSrc, "copilot", "prompts"), path.join(dest, ".github", "prompts"), ".github/prompts/");
|
|
137
|
+
} else if (c === "aider") console.log("SKIP: aider usa AGENTS.md raiz (nada que fusionar)");
|
|
138
|
+
}
|
|
139
|
+
function listFilesRec(dir, base) {
|
|
140
|
+
let out = [];
|
|
141
|
+
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
142
|
+
const p = path.join(dir, e.name);
|
|
143
|
+
if (e.isDirectory()) out = out.concat(listFilesRec(p, base));
|
|
144
|
+
else out.push(path.relative(base, p));
|
|
145
|
+
}
|
|
146
|
+
return out;
|
|
147
|
+
}
|
|
102
148
|
const frag = fs.readFileSync(path.join(src, "AI", ".gitignore.fragment"), "utf8")
|
|
103
149
|
.split(/\r?\n/).map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
|
|
104
150
|
let cur = [];
|
package/install.ps1
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
<# Instalador del harness BTP (idempotente, PowerShell 5.1).
|
|
2
|
-
Uso: .\install.ps1 [-Dest "."] [-Force]
|
|
2
|
+
Uso: .\install.ps1 [-Dest "."] [-Force] [-Client auto]
|
|
3
3
|
- Copia AI/ al proyecto destino (SKIP si ya existe; con -Force hace backup a AI.bak).
|
|
4
|
-
- Fusiona
|
|
4
|
+
- Fusiona el árbol del cliente agéntico desde AI/clients/<cliente>/ en la raíz
|
|
5
|
+
(solo faltantes; con -Force hace backup a <dir>.bak):
|
|
6
|
+
-Client auto (defecto): detecta por huellas (CLAUDE.md|.claude→claude,
|
|
7
|
+
.cursor→cursor, .github→copilot, opencode.json*|.opencode→opencode;
|
|
8
|
+
sin huellas→opencode) | all: todos | lista explícita (p. ej. claude,copilot).
|
|
5
9
|
- Crea AGENTS.md raíz desde la plantilla solo si no existe.
|
|
6
10
|
- Fusiona .gitignore con las líneas del fragmento (solo agrega faltantes).
|
|
7
11
|
- NO toca package.json ni mta.yaml del proyecto. Sin secretos. #>
|
|
8
12
|
param(
|
|
9
13
|
[string]$Dest = ".",
|
|
10
|
-
[switch]$Force
|
|
14
|
+
[switch]$Force,
|
|
15
|
+
$Client = "auto"
|
|
11
16
|
)
|
|
12
17
|
|
|
13
18
|
$ErrorActionPreference = "Stop"
|
|
@@ -50,35 +55,80 @@ if (Test-Path $AgentsDst) {
|
|
|
50
55
|
Write-Output "CREATE: AGENTS.md (puntero a AI/AGENTS.md)"
|
|
51
56
|
}
|
|
52
57
|
|
|
53
|
-
$
|
|
54
|
-
|
|
55
|
-
|
|
58
|
+
$ClientsSrc = Join-Path $AiSrc "clients"
|
|
59
|
+
|
|
60
|
+
function Get-ClientList($Spec, $DestDir) {
|
|
61
|
+
$s = (($Spec -join ",") + "").Trim().ToLower()
|
|
62
|
+
if ($s -eq "auto") {
|
|
63
|
+
$d = @()
|
|
64
|
+
if ((Test-Path (Join-Path $DestDir "CLAUDE.md")) -or (Test-Path (Join-Path $DestDir ".claude"))) { $d += "claude" }
|
|
65
|
+
if (Test-Path (Join-Path $DestDir ".cursor")) { $d += "cursor" }
|
|
66
|
+
if (Test-Path (Join-Path $DestDir ".github")) { $d += "copilot" }
|
|
67
|
+
$opHints = @(Get-ChildItem $DestDir -Filter "opencode.json*" -File -ErrorAction SilentlyContinue)
|
|
68
|
+
if (($opHints.Count -gt 0) -or (Test-Path (Join-Path $DestDir ".opencode"))) { $d += "opencode" }
|
|
69
|
+
if ($d.Count -eq 0) { $d = @("opencode") }
|
|
70
|
+
return $d
|
|
71
|
+
}
|
|
72
|
+
if ($s -eq "all") { return @("opencode", "claude", "cursor", "copilot") }
|
|
73
|
+
$list = @($s -split "," | ForEach-Object { $_.Trim().ToLower() } | Where-Object { $_ -ne "" })
|
|
74
|
+
$known = @("opencode", "claude", "cursor", "copilot", "aider")
|
|
75
|
+
foreach ($c in $list) {
|
|
76
|
+
if ($known -notcontains $c) { Write-Host "ABORT: cliente desconocido '$c' (usa auto|all|opencode,claude,cursor,copilot,aider)"; exit 1 }
|
|
77
|
+
}
|
|
78
|
+
if ($list.Count -eq 0) { Write-Host "ABORT: -Client vacío (usa auto|all|opencode,claude,cursor,copilot,aider)"; exit 1 }
|
|
79
|
+
return $list
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function Merge-Tree($TreeSrc, $TreeDst, $Label) {
|
|
83
|
+
if (-not (Test-Path $TreeSrc)) { Write-Output "AVISO: no existe $TreeSrc (regenera con node scripts/render-clients.js)"; return }
|
|
84
|
+
$BakLabel = $Label.TrimEnd("/") + ".bak"
|
|
56
85
|
if ($Force) {
|
|
57
|
-
if (Test-Path $
|
|
58
|
-
$
|
|
59
|
-
if (Test-Path $
|
|
60
|
-
Rename-Item $
|
|
61
|
-
Write-Output "BACKUP:
|
|
86
|
+
if (Test-Path $TreeDst) {
|
|
87
|
+
$Bak = "$TreeDst.bak"
|
|
88
|
+
if (Test-Path $Bak) { Remove-Item $Bak -Recurse -Force }
|
|
89
|
+
Rename-Item $TreeDst ([IO.Path]::GetFileName($Bak))
|
|
90
|
+
Write-Output "BACKUP: $Label -> $BakLabel"
|
|
62
91
|
}
|
|
63
|
-
Copy-Item $
|
|
64
|
-
$
|
|
65
|
-
Write-Output ("CREATE:
|
|
92
|
+
Copy-Item $TreeSrc $TreeDst -Recurse -Force
|
|
93
|
+
$TreeCount = @(Get-ChildItem $TreeDst -Recurse -File).Count
|
|
94
|
+
Write-Output ("CREATE: {0} (+{1} archivos)" -f $Label, $TreeCount)
|
|
66
95
|
} else {
|
|
67
|
-
$
|
|
68
|
-
$
|
|
69
|
-
foreach ($f in $
|
|
70
|
-
$rel = $f.FullName.Substring($
|
|
71
|
-
$tgt = Join-Path $
|
|
96
|
+
$TreeCount = 0
|
|
97
|
+
$TreeFiles = @(Get-ChildItem $TreeSrc -Recurse -File)
|
|
98
|
+
foreach ($f in $TreeFiles) {
|
|
99
|
+
$rel = $f.FullName.Substring($TreeSrc.Length + 1)
|
|
100
|
+
$tgt = Join-Path $TreeDst $rel
|
|
72
101
|
if (Test-Path $tgt) {
|
|
73
|
-
Write-Output ("SKIP:
|
|
102
|
+
Write-Output ("SKIP: {0}{1} ya existe" -f $Label, ($rel -replace "\\","/"))
|
|
74
103
|
} else {
|
|
75
104
|
$tgtDir = Split-Path -Parent $tgt
|
|
76
105
|
if (-not (Test-Path $tgtDir)) { New-Item $tgtDir -ItemType Directory -Force | Out-Null }
|
|
77
106
|
Copy-Item $f.FullName $tgt -Force
|
|
78
|
-
$
|
|
107
|
+
$TreeCount++
|
|
79
108
|
}
|
|
80
109
|
}
|
|
81
|
-
Write-Output ("CREATE:
|
|
110
|
+
Write-Output ("CREATE: {0} (+{1} archivos)" -f $Label, $TreeCount)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
$SelectedClients = @(Get-ClientList $Client $Dest)
|
|
115
|
+
if ((($Client -join ",") + "").Trim().ToLower() -eq "auto") {
|
|
116
|
+
Write-Output ("CLIENT: auto -> {0}" -f ($SelectedClients -join ","))
|
|
117
|
+
}
|
|
118
|
+
foreach ($c in $SelectedClients) {
|
|
119
|
+
if ($c -eq "opencode") {
|
|
120
|
+
Merge-Tree (Join-Path $ClientsSrc "opencode") (Join-Path $Dest ".opencode") ".opencode/"
|
|
121
|
+
} elseif ($c -eq "claude") {
|
|
122
|
+
Merge-Tree (Join-Path $ClientsSrc "claude") (Join-Path $Dest ".claude") ".claude/"
|
|
123
|
+
} elseif ($c -eq "cursor") {
|
|
124
|
+
Merge-Tree (Join-Path $ClientsSrc "cursor\commands") (Join-Path $Dest ".cursor\commands") ".cursor/commands/"
|
|
125
|
+
Merge-Tree (Join-Path $ClientsSrc "cursor\skills") (Join-Path $Dest ".agents\skills") ".agents/skills/"
|
|
126
|
+
} elseif ($c -eq "copilot") {
|
|
127
|
+
Merge-Tree (Join-Path $ClientsSrc "copilot\agents") (Join-Path $Dest ".github\agents") ".github/agents/"
|
|
128
|
+
Merge-Tree (Join-Path $ClientsSrc "copilot\skills") (Join-Path $Dest ".github\skills") ".github/skills/"
|
|
129
|
+
Merge-Tree (Join-Path $ClientsSrc "copilot\prompts") (Join-Path $Dest ".github\prompts") ".github/prompts/"
|
|
130
|
+
} elseif ($c -eq "aider") {
|
|
131
|
+
Write-Output "SKIP: aider usa AGENTS.md raiz (nada que fusionar)"
|
|
82
132
|
}
|
|
83
133
|
}
|
|
84
134
|
|
package/install.sh
CHANGED
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
2
|
# Instalador del harness BTP (idempotente, POSIX sh).
|
|
3
|
-
# Uso: ./install.sh [--dest <dir>] [--force] [--help]
|
|
3
|
+
# Uso: ./install.sh [--dest <dir>] [--force] [--client auto|all|opencode,claude,...] [--help]
|
|
4
4
|
# Sin secretos. No toca package.json ni mta.yaml del proyecto.
|
|
5
|
-
# Fusiona AI
|
|
6
|
-
# (solo faltantes; con --force hace backup a
|
|
5
|
+
# Fusiona el árbol del cliente agéntico desde AI/clients/<cliente>/ en la raíz
|
|
6
|
+
# (solo faltantes; con --force hace backup a <dir>.bak).
|
|
7
|
+
# --client auto (defecto): detecta por huellas (CLAUDE.md|.claude→claude,
|
|
8
|
+
# .cursor→cursor, .github→copilot, opencode.json*|.opencode→opencode;
|
|
9
|
+
# sin huellas→opencode). all: todos. Lista explícita: esos.
|
|
7
10
|
DEST="."
|
|
8
11
|
FORCE=0
|
|
12
|
+
CLIENT="auto"
|
|
9
13
|
while [ $# -gt 0 ]; do
|
|
10
14
|
case "$1" in
|
|
11
15
|
--dest) DEST="$2"; shift 2 ;;
|
|
12
16
|
--force) FORCE=1; shift ;;
|
|
17
|
+
--client) CLIENT="$2"; shift 2 ;;
|
|
13
18
|
-h|--help)
|
|
14
|
-
echo "Uso: ./install.sh [--dest <dir>] [--force] [--help]"
|
|
15
|
-
echo " --dest <dir>
|
|
16
|
-
echo " --force
|
|
17
|
-
echo "
|
|
19
|
+
echo "Uso: ./install.sh [--dest <dir>] [--force] [--client <clientes>] [--help]"
|
|
20
|
+
echo " --dest <dir> proyecto destino (por defecto: .)"
|
|
21
|
+
echo " --force reinstala AI/ con backup a AI.bak"
|
|
22
|
+
echo " --client <list> auto (defecto) | all | opencode,claude,cursor,copilot,aider"
|
|
23
|
+
echo " -h, --help muestra esta ayuda"
|
|
18
24
|
exit 0 ;;
|
|
19
25
|
*) echo "ABORT: opción desconocida $1 (usa --help)"; exit 1 ;;
|
|
20
26
|
esac
|
|
@@ -45,33 +51,87 @@ else
|
|
|
45
51
|
cp "$SRC/templates/AGENTS.pointer.md" "$DEST/AGENTS.md" && echo "CREATE: AGENTS.md (puntero a AI/AGENTS.md)"
|
|
46
52
|
fi
|
|
47
53
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
54
|
+
# Fusion idempotente de un arbol: merge_tree <origen> <destino> <etiqueta>
|
|
55
|
+
# (etiqueta p. ej. ".opencode/"; el backup va a <destino>.bak).
|
|
56
|
+
merge_tree() {
|
|
57
|
+
MT_SRC="$1"; MT_DST="$2"; MT_LABEL="$3"
|
|
58
|
+
if [ ! -d "$MT_SRC" ]; then echo "AVISO: no existe $MT_SRC (regenera con node scripts/render-clients.js)"; return 0; fi
|
|
59
|
+
MT_BAKLABEL="$(printf '%s' "$MT_LABEL" | sed 's:/$::').bak"
|
|
51
60
|
if [ "$FORCE" -eq 1 ]; then
|
|
52
|
-
if [ -d "$
|
|
53
|
-
rm -rf "$
|
|
54
|
-
echo "BACKUP:
|
|
61
|
+
if [ -d "$MT_DST" ]; then
|
|
62
|
+
rm -rf "$MT_DST.bak" && mv "$MT_DST" "$MT_DST.bak"
|
|
63
|
+
echo "BACKUP: $MT_LABEL -> $MT_BAKLABEL"
|
|
55
64
|
fi
|
|
56
|
-
|
|
65
|
+
mkdir -p "$(dirname "$MT_DST")"
|
|
66
|
+
cp -r "$MT_SRC" "$MT_DST" && MT_COUNT=$(find "$MT_DST" -type f | wc -l) && echo "CREATE: $MT_LABEL (+$MT_COUNT archivos)"
|
|
57
67
|
else
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
find "$
|
|
61
|
-
while IFS= read -r
|
|
62
|
-
|
|
63
|
-
if [ -f "$
|
|
64
|
-
echo "SKIP:
|
|
68
|
+
MT_COUNT=0
|
|
69
|
+
MT_LIST="${TMPDIR:-/tmp}/harness-mt-$$.tmp"
|
|
70
|
+
find "$MT_SRC" -type f -print > "$MT_LIST"
|
|
71
|
+
while IFS= read -r MT_FILE; do
|
|
72
|
+
MT_REL=${MT_FILE#"$MT_SRC"/}
|
|
73
|
+
if [ -f "$MT_DST/$MT_REL" ]; then
|
|
74
|
+
echo "SKIP: $MT_LABEL$MT_REL ya existe"
|
|
65
75
|
else
|
|
66
|
-
mkdir -p "$(dirname "$
|
|
67
|
-
cp "$
|
|
68
|
-
|
|
76
|
+
mkdir -p "$(dirname "$MT_DST/$MT_REL")"
|
|
77
|
+
cp "$MT_FILE" "$MT_DST/$MT_REL"
|
|
78
|
+
MT_COUNT=$((MT_COUNT + 1))
|
|
69
79
|
fi
|
|
70
|
-
done < "$
|
|
71
|
-
rm -f "$
|
|
72
|
-
echo "CREATE:
|
|
80
|
+
done < "$MT_LIST"
|
|
81
|
+
rm -f "$MT_LIST"
|
|
82
|
+
echo "CREATE: $MT_LABEL (+$MT_COUNT archivos)"
|
|
73
83
|
fi
|
|
74
|
-
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
# Resuelve la lista de clientes: detecta huellas en auto, valida explícitos.
|
|
87
|
+
resolve_clients() {
|
|
88
|
+
case "$CLIENT" in
|
|
89
|
+
auto)
|
|
90
|
+
RC_LIST=""
|
|
91
|
+
{ [ -f "$DEST/CLAUDE.md" ] || [ -d "$DEST/.claude" ]; } && RC_LIST="$RC_LIST claude"
|
|
92
|
+
[ -d "$DEST/.cursor" ] && RC_LIST="$RC_LIST cursor"
|
|
93
|
+
[ -d "$DEST/.github" ] && RC_LIST="$RC_LIST copilot"
|
|
94
|
+
if ls "$DEST"/opencode.json* >/dev/null 2>&1 || [ -d "$DEST/.opencode" ]; then RC_LIST="$RC_LIST opencode"; fi
|
|
95
|
+
[ -z "$RC_LIST" ] && RC_LIST=" opencode"
|
|
96
|
+
echo "CLIENT: auto -> $(printf '%s' "$RC_LIST" | tr ' ' ',' | sed 's/^,//')" >&2
|
|
97
|
+
printf '%s' "$RC_LIST"
|
|
98
|
+
;;
|
|
99
|
+
all) printf ' opencode claude cursor copilot' ;;
|
|
100
|
+
*)
|
|
101
|
+
RC_OUT=""
|
|
102
|
+
RC_REST="$CLIENT,"
|
|
103
|
+
while [ -n "$RC_REST" ]; do
|
|
104
|
+
RC_ONE=${RC_REST%%,*}; RC_REST=${RC_REST#*,}
|
|
105
|
+
RC_ONE=$(printf '%s' "$RC_ONE" | tr 'A-Z' 'a-z' | tr -d ' ')
|
|
106
|
+
[ -z "$RC_ONE" ] && continue
|
|
107
|
+
case "$RC_ONE" in
|
|
108
|
+
opencode|claude|cursor|copilot|aider) RC_OUT="$RC_OUT $RC_ONE" ;;
|
|
109
|
+
*) echo "ABORT: cliente desconocido '$RC_ONE' (usa auto|all|opencode,claude,cursor,copilot,aider)" >&2; exit 1 ;;
|
|
110
|
+
esac
|
|
111
|
+
done
|
|
112
|
+
[ -z "$RC_OUT" ] && { echo "ABORT: --client vacío (usa auto|all|opencode,claude,cursor,copilot,aider)" >&2; exit 1; }
|
|
113
|
+
printf '%s' "$RC_OUT"
|
|
114
|
+
;;
|
|
115
|
+
esac
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
CLIENTS_SRC="$SRC/AI/clients"
|
|
119
|
+
RC_RESOLVED=$(resolve_clients); RC_STATUS=$?
|
|
120
|
+
if [ "$RC_STATUS" -ne 0 ]; then exit "$RC_STATUS"; fi
|
|
121
|
+
for C in $RC_RESOLVED; do
|
|
122
|
+
case "$C" in
|
|
123
|
+
opencode) merge_tree "$CLIENTS_SRC/opencode" "$DEST/.opencode" ".opencode/" ;;
|
|
124
|
+
claude) merge_tree "$CLIENTS_SRC/claude" "$DEST/.claude" ".claude/" ;;
|
|
125
|
+
cursor)
|
|
126
|
+
merge_tree "$CLIENTS_SRC/cursor/commands" "$DEST/.cursor/commands" ".cursor/commands/"
|
|
127
|
+
merge_tree "$CLIENTS_SRC/cursor/skills" "$DEST/.agents/skills" ".agents/skills/" ;;
|
|
128
|
+
copilot)
|
|
129
|
+
merge_tree "$CLIENTS_SRC/copilot/agents" "$DEST/.github/agents" ".github/agents/"
|
|
130
|
+
merge_tree "$CLIENTS_SRC/copilot/skills" "$DEST/.github/skills" ".github/skills/"
|
|
131
|
+
merge_tree "$CLIENTS_SRC/copilot/prompts" "$DEST/.github/prompts" ".github/prompts/" ;;
|
|
132
|
+
aider) echo "SKIP: aider usa AGENTS.md raiz (nada que fusionar)" ;;
|
|
133
|
+
esac
|
|
134
|
+
done
|
|
75
135
|
|
|
76
136
|
FRAG="$SRC/AI/.gitignore.fragment"
|
|
77
137
|
touch "$DEST/.gitignore"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-harness-btp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Instala el harness MVP SAP BTP como carpeta AI/ en cualquier proyecto",
|
|
5
5
|
"keywords": ["sap-btp", "opencode", "harness", "agents", "scaffolder", "create"],
|
|
6
6
|
"homepage": "https://github.com/horangelmillan/harness-btp#readme",
|