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.
Files changed (59) hide show
  1. package/README.md +94 -3
  2. package/package.json +1 -1
  3. package/src/commands/barrier.js +358 -0
  4. package/src/commands/discover.js +18 -0
  5. package/src/commands/help.js +99 -9
  6. package/src/commands/index.js +3 -0
  7. package/src/commands/init.js +40 -2
  8. package/src/commands/log.js +2 -1
  9. package/src/commands/note.js +24 -0
  10. package/src/commands/req.js +7 -1
  11. package/src/commands/roadmap.js +4 -3
  12. package/src/commands/ship.js +49 -0
  13. package/src/commands/update-harness.js +208 -0
  14. package/src/commands/update.js +206 -72
  15. package/src/config/index.js +5 -2
  16. package/src/forge/adapter.js +189 -0
  17. package/src/forge/resolve.js +191 -0
  18. package/src/generators/adr.js +13 -4
  19. package/src/generators/date.js +23 -0
  20. package/src/generators/init.js +229 -39
  21. package/src/generators/note.js +154 -0
  22. package/src/generators/req.js +88 -5
  23. package/src/generators/roadmap.js +96 -20
  24. package/src/i18n/locales/en-US.json +2 -1
  25. package/src/i18n/locales/es-ES.json +2 -1
  26. package/src/i18n/locales/pt-BR.json +2 -1
  27. package/src/identity/preset.js +22 -0
  28. package/src/integrations/assets/agents/architect.md +35 -0
  29. package/src/integrations/assets/agents/backend.md +32 -0
  30. package/src/integrations/assets/agents/code-quality.md +32 -0
  31. package/src/integrations/assets/agents/data.md +32 -0
  32. package/src/integrations/assets/agents/dba.md +32 -0
  33. package/src/integrations/assets/agents/frontend.md +32 -0
  34. package/src/integrations/assets/agents/iac.md +46 -0
  35. package/src/integrations/assets/agents/infra.md +34 -0
  36. package/src/integrations/assets/agents/qa.md +32 -0
  37. package/src/integrations/assets/agents/security.md +32 -0
  38. package/src/integrations/assets/agents/tooling.md +47 -0
  39. package/src/integrations/assets/agents/ux.md +32 -0
  40. package/src/integrations/assets/catalog.json +16 -2
  41. package/src/integrations/assets/skills/architecture.md +66 -0
  42. package/src/integrations/assets/skills/backend.md +67 -0
  43. package/src/integrations/assets/skills/code-quality.md +78 -0
  44. package/src/integrations/assets/skills/data.md +75 -0
  45. package/src/integrations/assets/skills/dba.md +77 -0
  46. package/src/integrations/assets/skills/frontend.md +70 -0
  47. package/src/integrations/assets/skills/iac.md +90 -0
  48. package/src/integrations/assets/skills/infra.md +62 -0
  49. package/src/integrations/assets/skills/qa.md +63 -0
  50. package/src/integrations/assets/skills/security.md +74 -0
  51. package/src/integrations/assets/skills/tooling.md +80 -0
  52. package/src/integrations/assets/skills/ux.md +70 -0
  53. package/src/integrations/catalog.js +25 -1
  54. package/src/integrations/index.js +20 -2
  55. package/src/integrations/render.js +57 -6
  56. package/src/lib/update-engine.js +217 -0
  57. package/src/ship/runner.js +454 -0
  58. package/src/validator/index.js +323 -111
  59. 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
+ };
@@ -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
- return s.toLowerCase().replace(/ /g, '-')
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 new Date().toISOString().slice(0, 10)
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 }