@pleaseai/ask 0.1.0 → 0.1.3

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 (101) hide show
  1. package/dist/agents.d.ts +2 -0
  2. package/dist/agents.d.ts.map +1 -0
  3. package/dist/agents.js +73 -0
  4. package/dist/agents.js.map +1 -0
  5. package/dist/concurrency.d.ts +12 -0
  6. package/dist/concurrency.d.ts.map +1 -0
  7. package/dist/concurrency.js +33 -0
  8. package/dist/concurrency.js.map +1 -0
  9. package/dist/config.d.ts +8 -0
  10. package/dist/config.d.ts.map +1 -0
  11. package/dist/config.js +28 -0
  12. package/dist/config.js.map +1 -0
  13. package/dist/ignore-files.d.ts +59 -0
  14. package/dist/ignore-files.d.ts.map +1 -0
  15. package/dist/ignore-files.js +205 -0
  16. package/dist/ignore-files.js.map +1 -0
  17. package/dist/index.d.ts +76 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +485 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/io.d.ts +55 -0
  22. package/dist/io.d.ts.map +1 -0
  23. package/dist/io.js +159 -0
  24. package/dist/io.js.map +1 -0
  25. package/dist/manifest/index.d.ts +26 -0
  26. package/dist/manifest/index.d.ts.map +1 -0
  27. package/dist/manifest/index.js +25 -0
  28. package/dist/manifest/index.js.map +1 -0
  29. package/dist/manifest/npm.d.ts +18 -0
  30. package/dist/manifest/npm.d.ts.map +1 -0
  31. package/dist/manifest/npm.js +155 -0
  32. package/dist/manifest/npm.js.map +1 -0
  33. package/dist/markers.d.ts +34 -0
  34. package/dist/markers.d.ts.map +1 -0
  35. package/dist/markers.js +82 -0
  36. package/dist/markers.js.map +1 -0
  37. package/dist/migrate-legacy.d.ts +24 -0
  38. package/dist/migrate-legacy.d.ts.map +1 -0
  39. package/dist/migrate-legacy.js +85 -0
  40. package/dist/migrate-legacy.js.map +1 -0
  41. package/dist/registry.d.ts +71 -0
  42. package/dist/registry.d.ts.map +1 -0
  43. package/dist/registry.js +201 -0
  44. package/dist/registry.js.map +1 -0
  45. package/dist/resolvers/index.d.ts +32 -0
  46. package/dist/resolvers/index.d.ts.map +1 -0
  47. package/dist/resolvers/index.js +17 -0
  48. package/dist/resolvers/index.js.map +1 -0
  49. package/dist/resolvers/maven.d.ts +33 -0
  50. package/dist/resolvers/maven.d.ts.map +1 -0
  51. package/dist/resolvers/maven.js +187 -0
  52. package/dist/resolvers/maven.js.map +1 -0
  53. package/dist/resolvers/npm.d.ts +13 -0
  54. package/dist/resolvers/npm.d.ts.map +1 -0
  55. package/dist/resolvers/npm.js +67 -0
  56. package/dist/resolvers/npm.js.map +1 -0
  57. package/dist/resolvers/pub.d.ts +13 -0
  58. package/dist/resolvers/pub.d.ts.map +1 -0
  59. package/dist/resolvers/pub.js +50 -0
  60. package/dist/resolvers/pub.js.map +1 -0
  61. package/dist/resolvers/pypi.d.ts +12 -0
  62. package/dist/resolvers/pypi.d.ts.map +1 -0
  63. package/dist/resolvers/pypi.js +60 -0
  64. package/dist/resolvers/pypi.js.map +1 -0
  65. package/dist/resolvers/utils.d.ts +15 -0
  66. package/dist/resolvers/utils.d.ts.map +1 -0
  67. package/dist/resolvers/utils.js +26 -0
  68. package/dist/resolvers/utils.js.map +1 -0
  69. package/dist/schemas.d.ts +490 -0
  70. package/dist/schemas.d.ts.map +1 -0
  71. package/dist/schemas.js +91 -0
  72. package/dist/schemas.js.map +1 -0
  73. package/dist/skill.d.ts +4 -0
  74. package/dist/skill.d.ts.map +1 -0
  75. package/dist/skill.js +53 -0
  76. package/dist/skill.js.map +1 -0
  77. package/dist/sources/github.d.ts +14 -0
  78. package/dist/sources/github.d.ts.map +1 -0
  79. package/dist/sources/github.js +114 -0
  80. package/dist/sources/github.js.map +1 -0
  81. package/dist/sources/index.d.ts +41 -0
  82. package/dist/sources/index.d.ts.map +1 -0
  83. package/dist/sources/index.js +14 -0
  84. package/dist/sources/index.js.map +1 -0
  85. package/dist/sources/llms-txt.d.ts +5 -0
  86. package/dist/sources/llms-txt.d.ts.map +1 -0
  87. package/dist/sources/llms-txt.js +33 -0
  88. package/dist/sources/llms-txt.js.map +1 -0
  89. package/dist/sources/npm.d.ts +14 -0
  90. package/dist/sources/npm.d.ts.map +1 -0
  91. package/dist/sources/npm.js +113 -0
  92. package/dist/sources/npm.js.map +1 -0
  93. package/dist/sources/web.d.ts +13 -0
  94. package/dist/sources/web.d.ts.map +1 -0
  95. package/dist/sources/web.js +143 -0
  96. package/dist/sources/web.js.map +1 -0
  97. package/dist/storage.d.ts +11 -0
  98. package/dist/storage.d.ts.map +1 -0
  99. package/dist/storage.js +76 -0
  100. package/dist/storage.js.map +1 -0
  101. package/package.json +9 -8
package/dist/io.js ADDED
@@ -0,0 +1,159 @@
1
+ import { createHash } from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { ConfigSchema, LockSchema } from './schemas.js';
5
+ /**
6
+ * Recursively sort object keys for deterministic JSON serialization.
7
+ * Arrays preserve their element order; only object keys are reordered.
8
+ */
9
+ function sortKeys(value) {
10
+ if (Array.isArray(value)) {
11
+ return value.map(sortKeys);
12
+ }
13
+ if (value !== null && typeof value === 'object') {
14
+ const entries = Object.entries(value)
15
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
16
+ .map(([k, v]) => [k, sortKeys(v)]);
17
+ return Object.fromEntries(entries);
18
+ }
19
+ return value;
20
+ }
21
+ /**
22
+ * Serialize a value to JSON with sorted keys, 2-space indent, and a trailing
23
+ * newline. Two calls with semantically equivalent input always produce the
24
+ * same byte string.
25
+ */
26
+ export function sortedJSON(value) {
27
+ return `${JSON.stringify(sortKeys(value), null, 2)}\n`;
28
+ }
29
+ const ENCODER = new TextEncoder();
30
+ const NUL = new Uint8Array([0]);
31
+ export function contentHash(files) {
32
+ const sorted = [...files].sort((a, b) => a.relpath < b.relpath ? -1 : a.relpath > b.relpath ? 1 : 0);
33
+ const hash = createHash('sha256');
34
+ for (const f of sorted) {
35
+ hash.update(ENCODER.encode(f.relpath));
36
+ hash.update(NUL);
37
+ hash.update(f.bytes ?? ENCODER.encode(f.content ?? ''));
38
+ hash.update(NUL);
39
+ }
40
+ return `sha256-${hash.digest('hex')}`;
41
+ }
42
+ const ASK_DIR = '.ask';
43
+ const CONFIG_FILE = 'config.json';
44
+ const LOCK_FILE = 'ask.lock';
45
+ export function getAskDir(projectDir) {
46
+ return path.join(projectDir, ASK_DIR);
47
+ }
48
+ export function getConfigPath(projectDir) {
49
+ return path.join(getAskDir(projectDir), CONFIG_FILE);
50
+ }
51
+ export function getLockPath(projectDir) {
52
+ return path.join(getAskDir(projectDir), LOCK_FILE);
53
+ }
54
+ /**
55
+ * Read and validate `.ask/config.json`. Returns the default empty config when
56
+ * the file does not exist. Throws on invalid contents.
57
+ */
58
+ export function readConfig(projectDir) {
59
+ const file = getConfigPath(projectDir);
60
+ if (!fs.existsSync(file)) {
61
+ return { schemaVersion: 1, docs: [] };
62
+ }
63
+ const raw = fs.readFileSync(file, 'utf-8');
64
+ let parsed;
65
+ try {
66
+ parsed = JSON.parse(raw);
67
+ }
68
+ catch (err) {
69
+ throw new Error(`Failed to parse ${file}: ${err instanceof Error ? err.message : err}. `
70
+ + 'The file may be corrupt — delete it and re-run `ask docs sync` to regenerate.');
71
+ }
72
+ return ConfigSchema.parse(parsed);
73
+ }
74
+ /**
75
+ * Validate, sort, and write `.ask/config.json`. Sorts `docs[]` by name.
76
+ * Throws (without writing) if the input fails Zod validation.
77
+ */
78
+ export function writeConfig(projectDir, config) {
79
+ const validated = ConfigSchema.parse(config);
80
+ const sortedDocs = [...validated.docs].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
81
+ const out = { ...validated, docs: sortedDocs };
82
+ const file = getConfigPath(projectDir);
83
+ fs.mkdirSync(path.dirname(file), { recursive: true });
84
+ fs.writeFileSync(file, sortedJSON(out), 'utf-8');
85
+ }
86
+ /**
87
+ * Read and validate `.ask/ask.lock`. Returns the default empty lock when the
88
+ * file does not exist. Throws on invalid contents.
89
+ */
90
+ export function readLock(projectDir) {
91
+ const file = getLockPath(projectDir);
92
+ if (!fs.existsSync(file)) {
93
+ return {
94
+ lockfileVersion: 1,
95
+ generatedAt: '1970-01-01T00:00:00Z',
96
+ entries: {},
97
+ };
98
+ }
99
+ const raw = fs.readFileSync(file, 'utf-8');
100
+ let parsed;
101
+ try {
102
+ parsed = JSON.parse(raw);
103
+ }
104
+ catch (err) {
105
+ throw new Error(`Failed to parse ${file}: ${err instanceof Error ? err.message : err}. `
106
+ + 'The file may be corrupt — delete it and re-run `ask docs sync` to regenerate.');
107
+ }
108
+ return LockSchema.parse(parsed);
109
+ }
110
+ /**
111
+ * Validate, sort, and write `.ask/ask.lock`. Throws (without writing) if the
112
+ * input fails Zod validation.
113
+ */
114
+ export function writeLock(projectDir, lock) {
115
+ const validated = LockSchema.parse(lock);
116
+ const file = getLockPath(projectDir);
117
+ fs.mkdirSync(path.dirname(file), { recursive: true });
118
+ fs.writeFileSync(file, sortedJSON(validated), 'utf-8');
119
+ }
120
+ /**
121
+ * Upsert a single entry into `.ask/ask.lock`. Updates `generatedAt` only when
122
+ * the entry actually changes (so byte-stable on no-op re-runs).
123
+ */
124
+ export function upsertLockEntry(projectDir, name, entry) {
125
+ const lock = readLock(projectDir);
126
+ const previous = lock.entries[name];
127
+ const changed = !previous
128
+ || sortedJSON(stripFetchedAt(previous)) !== sortedJSON(stripFetchedAt(entry));
129
+ // No-op short circuit: if nothing changed (modulo fetchedAt), don't rewrite
130
+ // the file at all. This preserves mtime for build caches and file watchers.
131
+ if (!changed) {
132
+ return;
133
+ }
134
+ writeLock(projectDir, {
135
+ lockfileVersion: 1,
136
+ generatedAt: new Date().toISOString(),
137
+ entries: { ...lock.entries, [name]: entry },
138
+ });
139
+ }
140
+ function stripFetchedAt(entry) {
141
+ const { fetchedAt: _, ...rest } = entry;
142
+ return rest;
143
+ }
144
+ /**
145
+ * Remove one or more entries from the lock by name. No-op if absent.
146
+ */
147
+ export function removeLockEntries(projectDir, names) {
148
+ if (names.length === 0)
149
+ return;
150
+ const lock = readLock(projectDir);
151
+ const set = new Set(names);
152
+ const remaining = Object.fromEntries(Object.entries(lock.entries).filter(([k]) => !set.has(k)));
153
+ writeLock(projectDir, {
154
+ lockfileVersion: 1,
155
+ generatedAt: new Date().toISOString(),
156
+ entries: remaining,
157
+ });
158
+ }
159
+ //# sourceMappingURL=io.js.map
package/dist/io.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"io.js","sourceRoot":"","sources":["../src/io.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Manifest reader — inspects a project's lockfiles and manifests to find the
3
+ * exact version (or range) of an installed dependency. This is used by the
4
+ * `add` command's "manifest gate" to auto-resolve versions when the user omits
5
+ * one (e.g. `ask docs add npm:next` in a project that has `next` in bun.lock).
6
+ *
7
+ * Readers are stateless pure functions over the filesystem, so they are easy
8
+ * to unit test with programmatic fixtures.
9
+ */
10
+ export interface ManifestHit {
11
+ /** The resolved version string (exact if `exact=true`, a range otherwise). */
12
+ version: string;
13
+ /** Human-readable provenance string (e.g. `'bun.lock'`, `'package.json'`). */
14
+ source: string;
15
+ /** Whether the version is an exact pin (from a lockfile) or a range. */
16
+ exact: boolean;
17
+ }
18
+ export interface ManifestReader {
19
+ /**
20
+ * Read the installed version of `name` from the project at `projectDir`.
21
+ * Returns `null` if the package is not tracked by any manifest/lockfile.
22
+ */
23
+ readInstalledVersion: (name: string, projectDir: string) => ManifestHit | null;
24
+ }
25
+ export declare function getReader(ecosystem: string): ManifestReader | undefined;
26
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/manifest/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,MAAM,WAAW,WAAW;IAC1B,8EAA8E;IAC9E,OAAO,EAAE,MAAM,CAAA;IACf,8EAA8E;IAC9E,MAAM,EAAE,MAAM,CAAA;IACd,wEAAwE;IACxE,KAAK,EAAE,OAAO,CAAA;CACf;AAED,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,oBAAoB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,WAAW,GAAG,IAAI,CAAA;CAC/E;AASD,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAOvE"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Manifest reader — inspects a project's lockfiles and manifests to find the
3
+ * exact version (or range) of an installed dependency. This is used by the
4
+ * `add` command's "manifest gate" to auto-resolve versions when the user omits
5
+ * one (e.g. `ask docs add npm:next` in a project that has `next` in bun.lock).
6
+ *
7
+ * Readers are stateless pure functions over the filesystem, so they are easy
8
+ * to unit test with programmatic fixtures.
9
+ */
10
+ import { NpmManifestReader } from './npm.js';
11
+ /**
12
+ * Return the reader for a given ecosystem. Currently only `npm` is supported;
13
+ * other ecosystems fall through to `undefined` and callers should skip the
14
+ * manifest gate.
15
+ */
16
+ const npmReader = new NpmManifestReader();
17
+ export function getReader(ecosystem) {
18
+ switch (ecosystem) {
19
+ case 'npm':
20
+ return npmReader;
21
+ default:
22
+ return undefined;
23
+ }
24
+ }
25
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/manifest/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAmB5C;;;;GAIG;AACH,MAAM,SAAS,GAAmB,IAAI,iBAAiB,EAAE,CAAA;AAEzD,MAAM,UAAU,SAAS,CAAC,SAAiB;IACzC,QAAQ,SAAS,EAAE,CAAC;QAClB,KAAK,KAAK;YACR,OAAO,SAAS,CAAA;QAClB;YACE,OAAO,SAAS,CAAA;IACpB,CAAC;AACH,CAAC"}
@@ -0,0 +1,18 @@
1
+ import type { ManifestHit, ManifestReader } from './index.js';
2
+ /**
3
+ * Lockfile / manifest reader for the npm ecosystem.
4
+ *
5
+ * Lookup order (first hit wins):
6
+ * 1. bun.lock (exact)
7
+ * 2. package-lock.json (exact)
8
+ * 3. pnpm-lock.yaml (exact)
9
+ * 4. yarn.lock (exact)
10
+ * 5. package.json (range — `exact: false`)
11
+ *
12
+ * Only the root project's files are consulted — workspace-specific lockfiles
13
+ * are out of scope for this track.
14
+ */
15
+ export declare class NpmManifestReader implements ManifestReader {
16
+ readInstalledVersion(name: string, projectDir: string): ManifestHit | null;
17
+ }
18
+ //# sourceMappingURL=npm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"npm.d.ts","sourceRoot":"","sources":["../../src/manifest/npm.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AA+I7D;;;;;;;;;;;;GAYG;AACH,qBAAa,iBAAkB,YAAW,cAAc;IACtD,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;CAyB3E"}
@@ -0,0 +1,155 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ const RE_REGEX_META = /[.*+?^${}()|[\]\\]/g;
4
+ /**
5
+ * Escape a string for safe use inside a dynamically-constructed regex.
6
+ */
7
+ function escapeRegex(input) {
8
+ return input.replace(RE_REGEX_META, '\\$&');
9
+ }
10
+ /**
11
+ * Read a text file, returning null if it does not exist or cannot be read.
12
+ */
13
+ function readFileSafe(filePath) {
14
+ try {
15
+ return fs.readFileSync(filePath, 'utf8');
16
+ }
17
+ catch {
18
+ return null;
19
+ }
20
+ }
21
+ /**
22
+ * Parse a `bun.lock` file to find the installed version of `name`.
23
+ *
24
+ * bun.lock is a text-based TOML-ish format. Dependencies appear as lines like:
25
+ *
26
+ * "next@15.0.3": {
27
+ * "next": ["next@15.0.3", ...],
28
+ *
29
+ * We look for a quoted `"<name>@<version>"` token anywhere in the file — the
30
+ * first match wins. Scoped names (`@scope/pkg@1.2.3`) are handled by allowing
31
+ * an optional leading `@` in the name and taking the LAST `@` as the version
32
+ * separator within the quoted token.
33
+ */
34
+ function readBunLock(content, name) {
35
+ // Match "<name>@<version>" where <version> has no `"` in it.
36
+ // For scoped names, the `@` in `@scope/pkg` is part of the name — so we
37
+ // escape the full name literally and then require `@<semver-ish>` after it.
38
+ const escaped = escapeRegex(name);
39
+ const re = new RegExp(`"${escaped}@([^"@][^"]*)"`);
40
+ const match = content.match(re);
41
+ return match ? match[1] : null;
42
+ }
43
+ /**
44
+ * Parse a `package-lock.json` (npm v2/v3 format).
45
+ *
46
+ * Looks under `packages["node_modules/<name>"].version` first (lockfileVersion
47
+ * 2+), then under `dependencies.<name>.version` (v1).
48
+ */
49
+ function readNpmLock(content, name) {
50
+ try {
51
+ const json = JSON.parse(content);
52
+ const pkgKey = `node_modules/${name}`;
53
+ const fromPackages = json.packages?.[pkgKey]?.version;
54
+ if (fromPackages)
55
+ return fromPackages;
56
+ const fromDeps = json.dependencies?.[name]?.version;
57
+ if (fromDeps)
58
+ return fromDeps;
59
+ return null;
60
+ }
61
+ catch {
62
+ return null;
63
+ }
64
+ }
65
+ /**
66
+ * Parse a `pnpm-lock.yaml` file (best-effort, regex-based).
67
+ *
68
+ * pnpm lockfiles are real YAML, but the shape we care about is regular:
69
+ * under `importers: '.': dependencies: <name>: ... version: <ver>` or
70
+ * the `packages:` map keyed as `'/<name>@<version>'`. YAML parsing without a
71
+ * dependency is fragile, so we scan for the `/<name>@<version>:` package key
72
+ * which is the most stable form across pnpm versions.
73
+ *
74
+ * LIMITATION: monorepo importers other than `.` are ignored. See the track
75
+ * spec (out-of-scope: "monorepo 워크스페이스별 별도 lockfile 처리").
76
+ */
77
+ function readPnpmLock(content, name) {
78
+ const escaped = escapeRegex(name);
79
+ // Match ` /<name>@1.2.3:` or ` '/<name>@1.2.3':` possibly with a suffix
80
+ // like `_peerhash` which pnpm appends for peer-dep disambiguation.
81
+ const re = new RegExp(`^\\s*'?/${escaped}@([^():\\s_]+)`, 'm');
82
+ const match = content.match(re);
83
+ return match ? match[1] : null;
84
+ }
85
+ /**
86
+ * Parse a `yarn.lock` file (Yarn classic v1 format).
87
+ *
88
+ * Entries look like:
89
+ *
90
+ * "next@^15.0.0", next@15.0.3:
91
+ * version "15.0.3"
92
+ *
93
+ * We locate a line mentioning `<name>@` (either quoted or bare) and then take
94
+ * the nearest following `version "<ver>"`.
95
+ */
96
+ function readYarnLock(content, name) {
97
+ const escaped = escapeRegex(name);
98
+ // Find an entry header that mentions this package, then capture the next
99
+ // `version "<ver>"` within the following block.
100
+ const re = new RegExp(`(?:^|[",\\s])${escaped}@[^\\n]*:\\s*\\n(?:\\s+[^\\n]*\\n)*?\\s+version\\s+"([^"]+)"`, 'm');
101
+ const match = content.match(re);
102
+ return match ? match[1] : null;
103
+ }
104
+ /**
105
+ * Parse `package.json` for a dependency range (not exact — callers should
106
+ * mark `exact: false` so that `NpmResolver` gets a chance to interpret it).
107
+ */
108
+ function readPackageJson(content, name) {
109
+ try {
110
+ const json = JSON.parse(content);
111
+ return (json.dependencies?.[name]
112
+ ?? json.devDependencies?.[name]
113
+ ?? json.peerDependencies?.[name]
114
+ ?? json.optionalDependencies?.[name]
115
+ ?? null);
116
+ }
117
+ catch {
118
+ return null;
119
+ }
120
+ }
121
+ /**
122
+ * Lockfile / manifest reader for the npm ecosystem.
123
+ *
124
+ * Lookup order (first hit wins):
125
+ * 1. bun.lock (exact)
126
+ * 2. package-lock.json (exact)
127
+ * 3. pnpm-lock.yaml (exact)
128
+ * 4. yarn.lock (exact)
129
+ * 5. package.json (range — `exact: false`)
130
+ *
131
+ * Only the root project's files are consulted — workspace-specific lockfiles
132
+ * are out of scope for this track.
133
+ */
134
+ export class NpmManifestReader {
135
+ readInstalledVersion(name, projectDir) {
136
+ const candidates = [
137
+ { file: 'bun.lock', parser: readBunLock, exact: true },
138
+ { file: 'package-lock.json', parser: readNpmLock, exact: true },
139
+ { file: 'pnpm-lock.yaml', parser: readPnpmLock, exact: true },
140
+ { file: 'yarn.lock', parser: readYarnLock, exact: true },
141
+ { file: 'package.json', parser: readPackageJson, exact: false },
142
+ ];
143
+ for (const { file, parser, exact } of candidates) {
144
+ const content = readFileSafe(path.join(projectDir, file));
145
+ if (content == null)
146
+ continue;
147
+ const version = parser(content, name);
148
+ if (version) {
149
+ return { version, source: file, exact };
150
+ }
151
+ }
152
+ return null;
153
+ }
154
+ }
155
+ //# sourceMappingURL=npm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"npm.js","sourceRoot":"","sources":["../../src/manifest/npm.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Marker block helpers for injecting ASK-owned content into user files
3
+ * without touching the surrounding user content.
4
+ *
5
+ * Two comment syntaxes are supported:
6
+ * - `html` — for Markdown files (`<!-- ask:start --> ... <!-- ask:end -->`)
7
+ * - `hash` — for ignore/properties files (`# ask:start ... # ask:end`)
8
+ */
9
+ export type MarkerSyntax = 'html' | 'hash';
10
+ /**
11
+ * Wrap a payload with begin/end markers for the given comment syntax.
12
+ * The output does not include a trailing newline.
13
+ */
14
+ export declare function wrap(payload: string, syntax: MarkerSyntax): string;
15
+ /**
16
+ * Inject or refresh a marker block in `content`. If a block with matching
17
+ * markers already exists, it is replaced in place. Otherwise the block is
18
+ * appended to the end of the file with a blank line separator.
19
+ *
20
+ * The function is deterministic and idempotent: calling `inject` twice with
21
+ * the same block produces the same output.
22
+ */
23
+ export declare function inject(content: string, block: string, syntax: MarkerSyntax): string;
24
+ /**
25
+ * Strip the marker block from `content` if present. Returns the content
26
+ * unchanged if no marker pair is found. Trailing blank lines around the
27
+ * removed block are normalised.
28
+ */
29
+ export declare function remove(content: string, syntax: MarkerSyntax): string;
30
+ /**
31
+ * Test whether a marker block of the given syntax exists in `content`.
32
+ */
33
+ export declare function has(content: string, syntax: MarkerSyntax): boolean;
34
+ //# sourceMappingURL=markers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markers.d.ts","sourceRoot":"","sources":["../src/markers.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,CAAA;AAoB1C;;;GAGG;AACH,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,MAAM,CAGlE;AAED;;;;;;;GAOG;AACH,wBAAgB,MAAM,CACpB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,YAAY,GACnB,MAAM,CAeR;AAED;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,MAAM,CAiBpE;AAED;;GAEG;AACH,wBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAKlE"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Marker block helpers for injecting ASK-owned content into user files
3
+ * without touching the surrounding user content.
4
+ *
5
+ * Two comment syntaxes are supported:
6
+ * - `html` — for Markdown files (`<!-- ask:start --> ... <!-- ask:end -->`)
7
+ * - `hash` — for ignore/properties files (`# ask:start ... # ask:end`)
8
+ */
9
+ const LEADING_WHITESPACE_RE = /^\s+/;
10
+ const MARKERS = {
11
+ html: {
12
+ begin: '<!-- ask:start -->',
13
+ end: '<!-- ask:end -->',
14
+ },
15
+ hash: {
16
+ begin: '# ask:start',
17
+ end: '# ask:end',
18
+ },
19
+ };
20
+ /**
21
+ * Wrap a payload with begin/end markers for the given comment syntax.
22
+ * The output does not include a trailing newline.
23
+ */
24
+ export function wrap(payload, syntax) {
25
+ const { begin, end } = MARKERS[syntax];
26
+ return `${begin}\n${payload}\n${end}`;
27
+ }
28
+ /**
29
+ * Inject or refresh a marker block in `content`. If a block with matching
30
+ * markers already exists, it is replaced in place. Otherwise the block is
31
+ * appended to the end of the file with a blank line separator.
32
+ *
33
+ * The function is deterministic and idempotent: calling `inject` twice with
34
+ * the same block produces the same output.
35
+ */
36
+ export function inject(content, block, syntax) {
37
+ const { begin, end } = MARKERS[syntax];
38
+ const beginIdx = content.indexOf(begin);
39
+ const endIdx = content.indexOf(end);
40
+ if (beginIdx !== -1 && endIdx !== -1 && endIdx > beginIdx) {
41
+ // Replace existing block in place.
42
+ return content.substring(0, beginIdx) + block + content.substring(endIdx + end.length);
43
+ }
44
+ // Append to end with a blank-line separator.
45
+ if (content.length === 0) {
46
+ return `${block}\n`;
47
+ }
48
+ return `${content.trimEnd()}\n\n${block}\n`;
49
+ }
50
+ /**
51
+ * Strip the marker block from `content` if present. Returns the content
52
+ * unchanged if no marker pair is found. Trailing blank lines around the
53
+ * removed block are normalised.
54
+ */
55
+ export function remove(content, syntax) {
56
+ const { begin, end } = MARKERS[syntax];
57
+ const beginIdx = content.indexOf(begin);
58
+ const endIdx = content.indexOf(end);
59
+ if (beginIdx === -1 || endIdx === -1 || endIdx <= beginIdx) {
60
+ return content;
61
+ }
62
+ const before = content.substring(0, beginIdx).trimEnd();
63
+ const after = content.substring(endIdx + end.length).replace(LEADING_WHITESPACE_RE, '');
64
+ if (before.length === 0 && after.length === 0)
65
+ return '';
66
+ if (before.length === 0)
67
+ return after.endsWith('\n') ? after : `${after}\n`;
68
+ if (after.length === 0)
69
+ return `${before}\n`;
70
+ const tail = after.endsWith('\n') ? after : `${after}\n`;
71
+ return `${before}\n\n${tail}`;
72
+ }
73
+ /**
74
+ * Test whether a marker block of the given syntax exists in `content`.
75
+ */
76
+ export function has(content, syntax) {
77
+ const { begin, end } = MARKERS[syntax];
78
+ const beginIdx = content.indexOf(begin);
79
+ const endIdx = content.indexOf(end);
80
+ return beginIdx !== -1 && endIdx !== -1 && endIdx > beginIdx;
81
+ }
82
+ //# sourceMappingURL=markers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markers.js","sourceRoot":"","sources":["../src/markers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * One-shot migration from `.please/` (used by ASK before April 2026) to
3
+ * `.ask/`. Triggered exactly once on the first CLI invocation in a project
4
+ * that still has the legacy layout.
5
+ *
6
+ * Idempotency sentinel: the presence of `.ask/config.json` (NOT just `.ask/`)
7
+ * marks the migration as complete. A bare `.ask/` directory may exist for
8
+ * other reasons (created by another tool, leftover from a partial run), so
9
+ * we require the config file specifically.
10
+ *
11
+ * Order of operations is critical for safety:
12
+ * 1. Parse and validate the legacy config FIRST (in memory).
13
+ * 2. Write the new config to .ask/config.json — atomic-ish, no destructive
14
+ * side effects yet.
15
+ * 3. Move .please/docs/* into .ask/docs/.
16
+ * 4. Remove the legacy config file.
17
+ *
18
+ * If step 1 or 2 throws, nothing on disk has changed and the next run can
19
+ * retry. If step 3 throws partway through, we leave the partial state and
20
+ * throw — the user must intervene rather than silently losing entries on
21
+ * the next run.
22
+ */
23
+ export declare function migrateLegacyWorkspace(projectDir: string): void;
24
+ //# sourceMappingURL=migrate-legacy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate-legacy.d.ts","sourceRoot":"","sources":["../src/migrate-legacy.ts"],"names":[],"mappings":"AAQA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAwE/D"}
@@ -0,0 +1,85 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { consola } from 'consola';
4
+ import { getAskDir, getConfigPath, writeConfig } from './io.js';
5
+ import { ConfigSchema } from './schemas.js';
6
+ const LEGACY_DIR = '.please';
7
+ /**
8
+ * One-shot migration from `.please/` (used by ASK before April 2026) to
9
+ * `.ask/`. Triggered exactly once on the first CLI invocation in a project
10
+ * that still has the legacy layout.
11
+ *
12
+ * Idempotency sentinel: the presence of `.ask/config.json` (NOT just `.ask/`)
13
+ * marks the migration as complete. A bare `.ask/` directory may exist for
14
+ * other reasons (created by another tool, leftover from a partial run), so
15
+ * we require the config file specifically.
16
+ *
17
+ * Order of operations is critical for safety:
18
+ * 1. Parse and validate the legacy config FIRST (in memory).
19
+ * 2. Write the new config to .ask/config.json — atomic-ish, no destructive
20
+ * side effects yet.
21
+ * 3. Move .please/docs/* into .ask/docs/.
22
+ * 4. Remove the legacy config file.
23
+ *
24
+ * If step 1 or 2 throws, nothing on disk has changed and the next run can
25
+ * retry. If step 3 throws partway through, we leave the partial state and
26
+ * throw — the user must intervene rather than silently losing entries on
27
+ * the next run.
28
+ */
29
+ export function migrateLegacyWorkspace(projectDir) {
30
+ const newDir = getAskDir(projectDir);
31
+ const newConfig = getConfigPath(projectDir);
32
+ if (fs.existsSync(newConfig)) {
33
+ return;
34
+ }
35
+ const legacyDir = path.join(projectDir, LEGACY_DIR);
36
+ const legacyDocs = path.join(legacyDir, 'docs');
37
+ const legacyConfig = path.join(legacyDir, 'config.json');
38
+ const hasLegacyDocs = fs.existsSync(legacyDocs);
39
+ const hasLegacyConfig = fs.existsSync(legacyConfig);
40
+ if (!hasLegacyDocs && !hasLegacyConfig) {
41
+ return;
42
+ }
43
+ consola.warn('ASK legacy layout detected (.please/). Migrating to .ask/. '
44
+ + 'This is a one-time operation; future runs will not log this message.');
45
+ let migratedConfig = null;
46
+ if (hasLegacyConfig) {
47
+ try {
48
+ const raw = fs.readFileSync(legacyConfig, 'utf-8');
49
+ const legacy = JSON.parse(raw);
50
+ migratedConfig = ConfigSchema.parse({
51
+ schemaVersion: 1,
52
+ docs: legacy.docs ?? [],
53
+ });
54
+ }
55
+ catch (err) {
56
+ throw new Error(`Failed to parse legacy .please/config.json: ${err instanceof Error ? err.message : err}. `
57
+ + 'The legacy file is left intact for manual recovery — fix or delete it and re-run.');
58
+ }
59
+ }
60
+ fs.mkdirSync(newDir, { recursive: true });
61
+ writeConfig(projectDir, migratedConfig ?? { schemaVersion: 1, docs: [] });
62
+ if (hasLegacyDocs) {
63
+ const newDocs = path.join(newDir, 'docs');
64
+ fs.mkdirSync(newDocs, { recursive: true });
65
+ try {
66
+ for (const entry of fs.readdirSync(legacyDocs)) {
67
+ fs.renameSync(path.join(legacyDocs, entry), path.join(newDocs, entry));
68
+ }
69
+ fs.rmSync(legacyDocs, { recursive: true, force: true });
70
+ }
71
+ catch (err) {
72
+ throw new Error(`Failed to move legacy docs from .please/docs to .ask/docs: ${err instanceof Error ? err.message : err}. `
73
+ + 'Workspace is now in a partial state — please move any remaining entries manually.');
74
+ }
75
+ }
76
+ if (hasLegacyConfig) {
77
+ try {
78
+ fs.rmSync(legacyConfig, { force: true });
79
+ }
80
+ catch {
81
+ // best-effort: the new config is the sentinel, not the absence of the old one
82
+ }
83
+ }
84
+ }
85
+ //# sourceMappingURL=migrate-legacy.js.map