@dzhechkov/harness-cli 0.7.4 → 0.7.5

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.
@@ -0,0 +1,86 @@
1
+ /**
2
+ * `dz install` argument resolution — feature install-spec-honesty (backlog 43a52cf2 + c999786b).
3
+ *
4
+ * cmdInstall used to splice the RAW argument into `join(projectRoot, 'node_modules', pkg)`.
5
+ * MEASURED, five broken forms: an absolute path (path.join CONCATENATES an absolute segment —
6
+ * `/proj/node_modules/home/dz/pkgs/...`); a versioned spec `@dzhechkov/health-advisor@1.9.0`
7
+ * (the WORST: `npm install` SUCCEEDS and mutates the project's package.json, then dz dies on an
8
+ * invented path with exit 1 — and the versioned form is exactly what the npm-lag README
9
+ * prescribes); a tarball `pack-1.0.0.tgz`; `github:user/repo`; an alias `ha@npm:...`.
10
+ *
11
+ * The cure is a PURE resolver that runs BEFORE any npm process: every accepted form yields the
12
+ * npm spec AND the node_modules directory name it will land under; everything else is a NAMED
13
+ * refusal carrying the working two-command form. A spec whose landing dir cannot be established
14
+ * must never reach npm — that is what made the versioned failure mutate state and then die.
15
+ */
16
+
17
+ export interface InstallSpecProbe {
18
+ /** Does `path` exist as a regular file? */
19
+ readonly isFile: (path: string) => boolean;
20
+ /** Does `path` exist as a directory? */
21
+ readonly isDir: (path: string) => boolean;
22
+ /** `name` from `package/package.json` inside a tarball, or null (unreadable/absent). */
23
+ readonly readTarballName: (path: string) => string | null;
24
+ /** `name` from `<dir>/package.json`, or null. */
25
+ readonly readDirName: (path: string) => string | null;
26
+ }
27
+
28
+ export type InstallSpecResolution =
29
+ | { readonly kind: 'name' | 'versioned' | 'alias' | 'tarball' | 'dir'; readonly npmSpec: string; readonly dirName: string }
30
+ | { readonly kind: 'refused'; readonly reason: string; readonly hint: string };
31
+
32
+ const BARE_NAME_RE = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
33
+
34
+ /** The two-command fallback that always works, named in every refusal (43a52cf2's own ask). */
35
+ function twoCommandHint(raw: string): string {
36
+ return `working form: npm install ${raw} --save-dev && dz init --skills-dir node_modules/<installed-package-name>`;
37
+ }
38
+
39
+ export function resolveInstallSpec(raw: string, resolveAgainstCwd: (p: string) => string, probe: InstallSpecProbe): InstallSpecResolution {
40
+ const spec = raw.trim();
41
+ if (spec === '') return { kind: 'refused', reason: 'empty spec', hint: 'dz install <package|package@version|path/to.tgz|path/to/dir>' };
42
+
43
+ // Path forms first — a path is never an npm name. Absolute, explicit-relative, or an existing
44
+ // .tgz/.tar.gz/.dir anywhere: the OLD code spliced these into node_modules verbatim.
45
+ const looksPathy = spec.startsWith('/') || spec.startsWith('./') || spec.startsWith('../') || /\.(tgz|tar\.gz)$/.test(spec);
46
+ if (looksPathy) {
47
+ const abs = resolveAgainstCwd(spec);
48
+ if (/\.(tgz|tar\.gz)$/.test(spec)) {
49
+ if (!probe.isFile(abs)) return { kind: 'refused', reason: `tarball not found: ${abs}`, hint: twoCommandHint(spec) };
50
+ const name = probe.readTarballName(abs);
51
+ if (name === null) return { kind: 'refused', reason: `cannot read package/package.json name from ${abs}`, hint: twoCommandHint(spec) };
52
+ return { kind: 'tarball', npmSpec: abs, dirName: name };
53
+ }
54
+ if (probe.isDir(abs)) {
55
+ const name = probe.readDirName(abs);
56
+ if (name === null) return { kind: 'refused', reason: `${abs} has no readable package.json name`, hint: twoCommandHint(spec) };
57
+ return { kind: 'dir', npmSpec: abs, dirName: name };
58
+ }
59
+ return { kind: 'refused', reason: `path does not exist: ${abs}`, hint: twoCommandHint(spec) };
60
+ }
61
+
62
+ // git/github/url specs: the landing dir is not cheaply establishable — refuse with the cure.
63
+ if (/^(github:|git\+|git:|https?:\/\/)/.test(spec)) {
64
+ return { kind: 'refused', reason: `git/url specs are not resolvable to a node_modules dir up front (${spec})`, hint: twoCommandHint(spec) };
65
+ }
66
+
67
+ // alias@npm:real[@version] — installs under the ALIAS name.
68
+ const alias = /^([^@][^@]*|@[^/]+\/[^@]+)@npm:(.+)$/.exec(spec);
69
+ if (alias !== null) {
70
+ return { kind: 'alias', npmSpec: spec, dirName: alias[1] as string };
71
+ }
72
+
73
+ if (BARE_NAME_RE.test(spec)) return { kind: 'name', npmSpec: spec, dirName: spec };
74
+
75
+ // name@version / @scope/name@version — the dir is the name WITHOUT the version.
76
+ const at = spec.lastIndexOf('@');
77
+ if (at > 0) {
78
+ const name = spec.slice(0, at);
79
+ const version = spec.slice(at + 1);
80
+ if (BARE_NAME_RE.test(name) && version !== '' && !version.includes('/')) {
81
+ return { kind: 'versioned', npmSpec: spec, dirName: name };
82
+ }
83
+ }
84
+
85
+ return { kind: 'refused', reason: `unrecognized install spec: ${spec}`, hint: twoCommandHint(spec) };
86
+ }
@@ -38,6 +38,7 @@ export const KNOWN_CLI_FLAGS: readonly string[] = [
38
38
  'candidates',
39
39
  'canonical',
40
40
  'category',
41
+ 'channel',
41
42
  'check',
42
43
  'claim-check',
43
44
  'claude-bin',
@@ -52,6 +53,7 @@ export const KNOWN_CLI_FLAGS: readonly string[] = [
52
53
  'confirmed',
53
54
  'context-only',
54
55
  'corroborate',
56
+ 'count-project',
55
57
  'day',
56
58
  'deep',
57
59
  'default-family',
@@ -62,6 +64,7 @@ export const KNOWN_CLI_FLAGS: readonly string[] = [
62
64
  'dir',
63
65
  'domain',
64
66
  'done',
67
+ 'draft',
65
68
  'dry-run',
66
69
  'effect',
67
70
  'effort',
@@ -142,6 +145,7 @@ export const KNOWN_CLI_FLAGS: readonly string[] = [
142
145
  'n',
143
146
  'name',
144
147
  'name-only',
148
+ 'night',
145
149
  'no-audit',
146
150
  'no-decorate',
147
151
  'no-dry-run',
@@ -183,6 +187,7 @@ export const KNOWN_CLI_FLAGS: readonly string[] = [
183
187
  'porcelain',
184
188
  'preset',
185
189
  'pretty',
190
+ 'preview',
186
191
  'project',
187
192
  'promote',
188
193
  'proposal',
@@ -197,6 +202,7 @@ export const KNOWN_CLI_FLAGS: readonly string[] = [
197
202
  'recalled',
198
203
  'reconcile',
199
204
  'record-provisional',
205
+ 'refresh-publishes',
200
206
  'registry',
201
207
  'reinforce',
202
208
  'remove',
@@ -222,6 +228,7 @@ export const KNOWN_CLI_FLAGS: readonly string[] = [
222
228
  'seed',
223
229
  'select',
224
230
  'semantic',
231
+ 'send',
225
232
  'session',
226
233
  'sessions-dir',
227
234
  'short',
@@ -274,6 +281,7 @@ export const KNOWN_CLI_FLAGS: readonly string[] = [
274
281
  'week',
275
282
  'weekly',
276
283
  'window-days',
284
+ 'window',
277
285
  'with-pairs',
278
286
  'with-references',
279
287
  'work-order',