trackfw 3.0.0 → 4.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/README.md +94 -3
- package/package.json +1 -1
- package/src/commands/barrier.js +358 -0
- package/src/commands/discover.js +18 -0
- package/src/commands/help.js +99 -9
- package/src/commands/index.js +3 -0
- package/src/commands/init.js +40 -2
- package/src/commands/log.js +2 -1
- package/src/commands/note.js +24 -0
- package/src/commands/req.js +7 -1
- package/src/commands/roadmap.js +4 -3
- package/src/commands/ship.js +49 -0
- package/src/commands/update-harness.js +208 -0
- package/src/commands/update.js +206 -72
- package/src/config/index.js +5 -2
- package/src/forge/adapter.js +189 -0
- package/src/forge/resolve.js +191 -0
- package/src/generators/adr.js +13 -4
- package/src/generators/date.js +23 -0
- package/src/generators/init.js +229 -39
- package/src/generators/note.js +154 -0
- package/src/generators/req.js +88 -5
- package/src/generators/roadmap.js +96 -20
- package/src/i18n/locales/en-US.json +2 -1
- package/src/i18n/locales/es-ES.json +2 -1
- package/src/i18n/locales/pt-BR.json +2 -1
- package/src/identity/preset.js +22 -0
- package/src/integrations/assets/agents/architect.md +35 -0
- package/src/integrations/assets/agents/backend.md +32 -0
- package/src/integrations/assets/agents/code-quality.md +32 -0
- package/src/integrations/assets/agents/data.md +32 -0
- package/src/integrations/assets/agents/dba.md +32 -0
- package/src/integrations/assets/agents/frontend.md +32 -0
- package/src/integrations/assets/agents/iac.md +46 -0
- package/src/integrations/assets/agents/infra.md +34 -0
- package/src/integrations/assets/agents/qa.md +32 -0
- package/src/integrations/assets/agents/security.md +32 -0
- package/src/integrations/assets/agents/tooling.md +47 -0
- package/src/integrations/assets/agents/ux.md +32 -0
- package/src/integrations/assets/catalog.json +16 -2
- package/src/integrations/assets/skills/architecture.md +66 -0
- package/src/integrations/assets/skills/backend.md +67 -0
- package/src/integrations/assets/skills/code-quality.md +78 -0
- package/src/integrations/assets/skills/data.md +75 -0
- package/src/integrations/assets/skills/dba.md +77 -0
- package/src/integrations/assets/skills/frontend.md +70 -0
- package/src/integrations/assets/skills/iac.md +90 -0
- package/src/integrations/assets/skills/infra.md +62 -0
- package/src/integrations/assets/skills/qa.md +63 -0
- package/src/integrations/assets/skills/security.md +74 -0
- package/src/integrations/assets/skills/tooling.md +80 -0
- package/src/integrations/assets/skills/ux.md +70 -0
- package/src/integrations/catalog.js +25 -1
- package/src/integrations/index.js +20 -2
- package/src/integrations/render.js +57 -6
- package/src/lib/update-engine.js +217 -0
- package/src/ship/runner.js +454 -0
- package/src/validator/index.js +323 -111
- package/src/validator/traceid.js +1 -1
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* forge/resolve.js — Forge resolution for trackfw ship.
|
|
5
|
+
*
|
|
6
|
+
* Precedence (highest → lowest):
|
|
7
|
+
* 1. --forge flag (explicit override)
|
|
8
|
+
* 2. forge: field in trackfw.yaml
|
|
9
|
+
* 3. Host extracted from git remote get-url origin
|
|
10
|
+
* 4. CI configuration files in repo root (.gitlab-ci.yml / .github/workflows/)
|
|
11
|
+
* 5. "manual" — never an error
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const { execSync } = require('child_process');
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
/** Accepted values for the --forge flag and forge: config field. */
|
|
19
|
+
const VALID_FORGES = ['github', 'gitlab', 'bitbucket', 'azure'];
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Resolve the forge from the given inputs.
|
|
23
|
+
*
|
|
24
|
+
* @param {object} input
|
|
25
|
+
* @param {string} [input.flagForge] - value from --forge flag
|
|
26
|
+
* @param {string} [input.configForge] - value from trackfw.yaml forge: field
|
|
27
|
+
* @param {string} [input.remoteURL] - output of "git remote get-url origin"
|
|
28
|
+
* @param {string} [input.repoDir] - repo root for CI file detection
|
|
29
|
+
* @returns {{ forge: string, source: string }} Resolution object.
|
|
30
|
+
* forge ∈ {"github", "gitlab", "bitbucket", "azure", "manual"}
|
|
31
|
+
* source ∈ {"flag", "config", "remote", "ci", "none"}
|
|
32
|
+
* @throws {Error} if flagForge or configForge contains an invalid value.
|
|
33
|
+
*/
|
|
34
|
+
function resolve(input) {
|
|
35
|
+
const { flagForge = '', configForge = '', remoteURL = '', repoDir = '' } = input || {};
|
|
36
|
+
|
|
37
|
+
// 1. Explicit flag wins everything.
|
|
38
|
+
if (flagForge) {
|
|
39
|
+
validateForge(flagForge);
|
|
40
|
+
return { forge: flagForge, source: 'flag' };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 2. Config field.
|
|
44
|
+
if (configForge) {
|
|
45
|
+
validateForge(configForge);
|
|
46
|
+
return { forge: configForge, source: 'config' };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 3. Remote URL — known hosts only.
|
|
50
|
+
if (remoteURL) {
|
|
51
|
+
const forge = forgeFromRemoteURL(remoteURL);
|
|
52
|
+
if (forge) return { forge, source: 'remote' };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 4. CI files — desempate for self-hosted / unknown host.
|
|
56
|
+
if (repoDir) {
|
|
57
|
+
const forge = forgeFromCI(repoDir);
|
|
58
|
+
if (forge) return { forge, source: 'ci' };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 5. Manual — not an error.
|
|
62
|
+
return { forge: 'manual', source: 'none' };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Production entry point: runs "git remote get-url origin" in repoDir,
|
|
67
|
+
* then calls resolve().
|
|
68
|
+
*/
|
|
69
|
+
function resolveFromRepo(flagForge, configForge, repoDir) {
|
|
70
|
+
const remoteURL = gitRemoteURL(repoDir);
|
|
71
|
+
return resolve({ flagForge, configForge, remoteURL, repoDir });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** @throws {Error} if forge is not in VALID_FORGES */
|
|
75
|
+
function validateForge(forge) {
|
|
76
|
+
if (!VALID_FORGES.includes(forge)) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
`invalid forge "${forge}": accepted values are ${VALID_FORGES.join(', ')}`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function forgeFromRemoteURL(rawURL) {
|
|
84
|
+
const host = extractHost(rawURL);
|
|
85
|
+
return hostToForge(host);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Extracts the lowercase hostname from HTTPS, SSH (git@) or ssh:// URLs.
|
|
90
|
+
*/
|
|
91
|
+
function extractHost(rawURL) {
|
|
92
|
+
if (!rawURL) return '';
|
|
93
|
+
rawURL = rawURL.trim();
|
|
94
|
+
|
|
95
|
+
// SSH short form: git@github.com:org/repo.git
|
|
96
|
+
if (rawURL.startsWith('git@')) {
|
|
97
|
+
const rest = rawURL.slice(4); // strip "git@"
|
|
98
|
+
const colonIdx = rest.indexOf(':');
|
|
99
|
+
if (colonIdx >= 0) return rest.slice(0, colonIdx).toLowerCase();
|
|
100
|
+
return '';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// SSH long form: ssh://git@github.com/org/repo.git
|
|
104
|
+
if (rawURL.startsWith('ssh://')) {
|
|
105
|
+
let rest = rawURL.slice(6); // strip "ssh://"
|
|
106
|
+
const atIdx = rest.indexOf('@');
|
|
107
|
+
if (atIdx >= 0) rest = rest.slice(atIdx + 1); // strip user@
|
|
108
|
+
const slashIdx = rest.indexOf('/');
|
|
109
|
+
return slashIdx >= 0
|
|
110
|
+
? rest.slice(0, slashIdx).toLowerCase()
|
|
111
|
+
: rest.toLowerCase();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// HTTPS / HTTP
|
|
115
|
+
if (rawURL.startsWith('https://') || rawURL.startsWith('http://')) {
|
|
116
|
+
let rest = rawURL.replace(/^https?:\/\//, '');
|
|
117
|
+
// strip optional user:pass@
|
|
118
|
+
const atIdx = rest.indexOf('@');
|
|
119
|
+
if (atIdx >= 0) rest = rest.slice(atIdx + 1);
|
|
120
|
+
const slashIdx = rest.indexOf('/');
|
|
121
|
+
return slashIdx >= 0
|
|
122
|
+
? rest.slice(0, slashIdx).toLowerCase()
|
|
123
|
+
: rest.toLowerCase();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return '';
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Maps a known hostname to its forge identifier.
|
|
131
|
+
* Azure DevOps uses dev.azure.com (HTTPS) and ssh.dev.azure.com (SSH).
|
|
132
|
+
* Returns '' for unknown / self-hosted hosts.
|
|
133
|
+
*/
|
|
134
|
+
function hostToForge(host) {
|
|
135
|
+
if (!host) return '';
|
|
136
|
+
if (host === 'github.com') return 'github';
|
|
137
|
+
if (host === 'gitlab.com') return 'gitlab';
|
|
138
|
+
if (host === 'bitbucket.org') return 'bitbucket';
|
|
139
|
+
if (
|
|
140
|
+
host === 'dev.azure.com' ||
|
|
141
|
+
host.endsWith('.dev.azure.com') ||
|
|
142
|
+
host.endsWith('.visualstudio.com')
|
|
143
|
+
) {
|
|
144
|
+
return 'azure';
|
|
145
|
+
}
|
|
146
|
+
return '';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Inspects CI indicator files in repoDir.
|
|
151
|
+
* Priority: .gitlab-ci.yml → gitlab; .github/workflows/ → github.
|
|
152
|
+
*/
|
|
153
|
+
function forgeFromCI(repoDir) {
|
|
154
|
+
try {
|
|
155
|
+
fs.statSync(path.join(repoDir, '.gitlab-ci.yml'));
|
|
156
|
+
return 'gitlab';
|
|
157
|
+
} catch (_) {}
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
const stat = fs.statSync(path.join(repoDir, '.github', 'workflows'));
|
|
161
|
+
if (stat.isDirectory()) return 'github';
|
|
162
|
+
} catch (_) {}
|
|
163
|
+
|
|
164
|
+
return '';
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Runs "git remote get-url origin" in repoDir.
|
|
169
|
+
* Returns '' on any error.
|
|
170
|
+
*/
|
|
171
|
+
function gitRemoteURL(repoDir) {
|
|
172
|
+
try {
|
|
173
|
+
return execSync('git remote get-url origin', {
|
|
174
|
+
cwd: repoDir,
|
|
175
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
176
|
+
encoding: 'utf8',
|
|
177
|
+
}).trim();
|
|
178
|
+
} catch (_) {
|
|
179
|
+
return '';
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
module.exports = {
|
|
184
|
+
VALID_FORGES,
|
|
185
|
+
resolve,
|
|
186
|
+
resolveFromRepo,
|
|
187
|
+
validateForge,
|
|
188
|
+
extractHost,
|
|
189
|
+
hostToForge,
|
|
190
|
+
forgeFromCI,
|
|
191
|
+
};
|
package/src/generators/adr.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs')
|
|
4
4
|
const path = require('path')
|
|
5
|
+
const { localDateISO } = require('./date')
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Converte uma string em slug: lowercase + espaços → hifens.
|
|
@@ -9,7 +10,13 @@ const path = require('path')
|
|
|
9
10
|
* @returns {string}
|
|
10
11
|
*/
|
|
11
12
|
function toSlug(s) {
|
|
12
|
-
|
|
13
|
+
// NFKD normalization + remove combining marks (diacríticos) + lowercase + non-alphanumeric → hífen
|
|
14
|
+
return s
|
|
15
|
+
.normalize('NFKD')
|
|
16
|
+
.replace(/[̀-ͯ]/g, '')
|
|
17
|
+
.toLowerCase()
|
|
18
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
19
|
+
.replace(/^-+|-+$/g, '')
|
|
13
20
|
}
|
|
14
21
|
|
|
15
22
|
/**
|
|
@@ -26,11 +33,13 @@ function slugToTitle(slug) {
|
|
|
26
33
|
}
|
|
27
34
|
|
|
28
35
|
/**
|
|
29
|
-
* Retorna a data atual no formato YYYY-MM-DD.
|
|
36
|
+
* Retorna a data LOCAL atual no formato YYYY-MM-DD.
|
|
37
|
+
* Delega para localDateISO() — usa getDate/getMonth/getFullYear (hora local),
|
|
38
|
+
* não toISOString (UTC).
|
|
30
39
|
* @returns {string}
|
|
31
40
|
*/
|
|
32
41
|
function today() {
|
|
33
|
-
return
|
|
42
|
+
return localDateISO()
|
|
34
43
|
}
|
|
35
44
|
|
|
36
45
|
/**
|
|
@@ -182,4 +191,4 @@ author: ""
|
|
|
182
191
|
return filename
|
|
183
192
|
}
|
|
184
193
|
|
|
185
|
-
module.exports = { newADR, listADRs, newADRDraft, toSlug }
|
|
194
|
+
module.exports = { newADR, listADRs, newADRDraft, toSlug, today }
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* localDateISO — retorna a data LOCAL atual no formato YYYY-MM-DD.
|
|
5
|
+
*
|
|
6
|
+
* Usa getFullYear/getMonth/getDate (métodos de hora local) em vez de
|
|
7
|
+
* toISOString() (que retorna sempre UTC). Isso garante que uma REQ criada às
|
|
8
|
+
* 22h no Brasil seja datada do dia correto, independentemente do fuso da
|
|
9
|
+
* máquina — comportamento idêntico ao de Go (time.Now().Format("2006-01-02"))
|
|
10
|
+
* e Python (date.today().isoformat()).
|
|
11
|
+
*
|
|
12
|
+
* @returns {string} Data no formato "YYYY-MM-DD"
|
|
13
|
+
*/
|
|
14
|
+
function localDateISO() {
|
|
15
|
+
const d = new Date()
|
|
16
|
+
return [
|
|
17
|
+
d.getFullYear(),
|
|
18
|
+
String(d.getMonth() + 1).padStart(2, '0'),
|
|
19
|
+
String(d.getDate()).padStart(2, '0'),
|
|
20
|
+
].join('-')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = { localDateISO }
|