@erclx/aitk 3.9.0 → 3.10.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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "aitk",
3
3
  "description": "Automated governance, versioning, and discovery tools for Claude Code.",
4
- "version": "3.9.0",
4
+ "version": "3.10.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -52,8 +52,6 @@ Run `aitk tooling sync <stack> <target> --check` for the list resolved against a
52
52
  - `.vscode/extensions.json`
53
53
  - `.vscode/settings.json`
54
54
  - `commitlint.config.js`
55
- - `scripts/clean.sh`
56
- - `scripts/update.sh`
57
55
  - `scripts/verify.sh`
58
56
 
59
57
  ### python
@@ -22,11 +22,11 @@ aitk audits list --json
22
22
 
23
23
  ## What it runs
24
24
 
25
- Seventeen verbs, listed by `aitk audits list`. Each runs once in its fullest form, and the aggregate reads that verb's own record rather than imposing a shared envelope on it. Every one of those records already has consumers naming its keys, so a common shape would be a breaking change bought for tidiness.
25
+ Eighteen verbs, listed by `aitk audits list`. Each runs once in its fullest form, and the aggregate reads that verb's own record rather than imposing a shared envelope on it. Every one of those records already has consumers naming its keys, so a common shape would be a breaking change bought for tidiness.
26
26
 
27
27
  The verbs walk separate trees and share no state, so they run together. Measured on the authoring machine at twelve verbs, a run finished in 0.8 seconds of wall clock against 4.4 seconds of processor, which is under every other stage in `bun run check`. `aitk deps audit` is the one that changes that reading, since it reaches a network rather than a tree and its latency is the index's rather than this machine's.
28
28
 
29
- Fourteen of the seventeen read a tree on this disk, the one added by `restated.md` among them, since it reads three such trees against each other. The two added by `state-scoped-risk.md` read committed state rather than an arriving change, which is the gap every review surface here leaves by construction, and the one added by `label-coverage.md` reads a branch range against a map the project declares.
29
+ Fifteen of the eighteen read a tree on this disk, the one added by `restated.md` and the one added by `census.md` among them, since the first reads three such trees against each other and the second counts every file in one. The two added by `state-scoped-risk.md` read committed state rather than an arriving change, which is the gap every review surface here leaves by construction, and the one added by `label-coverage.md` reads a branch range against a map the project declares.
30
30
 
31
31
  Each is invoked as the CLI the caller is running rather than as a global `aitk`. A globally installed binary resolves to the main checkout no matter which worktree is executing, so the aggregate would measure a tree the branch never touched and report a pass over it.
32
32
 
@@ -0,0 +1,23 @@
1
+ ---
2
+ title: Census
3
+ description: Tracked-plus-untracked file count, a breakdown by extension, and a line total that skips whatever reads as binary
4
+ ---
5
+
6
+ # Census
7
+
8
+ `aitk census [path]` reports how large a tree is: the tracked-plus-untracked file count, a breakdown by extension, and a line total. It reads `listRepositoryFiles`, the same corpus the citation check, the markdown corpus, and the secret scan already read, so this is not a fourth definition of what counts.
9
+
10
+ ```bash
11
+ aitk census
12
+ aitk census src --json
13
+ ```
14
+
15
+ | Option | Behavior |
16
+ | -------- | ---------------------------------------------------------- |
17
+ | `--json` | Add a machine-readable record on stdout, keeping the frame |
18
+
19
+ Grouping is by extension rather than by a named language category. A config file and a source file land in separate buckets and two source files sharing an extension land in one, and a file with no extension, such as `Dockerfile`, lands under `no-extension`.
20
+
21
+ Line counts are text-only. A file that reads as binary, and a file git lists that will not open, such as a symlink leaving the tree, are counted toward the file total and their extension's file count, and left out of every line count. The record's `skipped` field states that gap rather than leaving a reader to infer it from the difference between the file count and the summed extension line counts. It carries both causes as one number: on a healthy tree the two are indistinguishable, so a `skipped` count above what the tree's own binary files explain is itself the signal that something in the corpus will not open.
22
+
23
+ The command reports a snapshot rather than a delta. It registers in the audit catalog with an empty gating set, so `aitk audits run --record` is what turns two snapshots into the growth series a hand count used to approximate: 481 files on one date and 965 five weeks later, with nothing between them because nobody had counted again.
@@ -54,6 +54,7 @@ Full help: `aitk <command> --help`. Behavior notes for the install and sync verb
54
54
  | `aitk secrets scan` | Report credential-shaped values in the tree the package ships, keyed on issued values rather than on words (`--json`) |
55
55
  | `aitk deps audit` | Report published advisories against the resolved dependency set, refusing rather than reporting clean when the index is unreachable (`--json`) |
56
56
  | `aitk labels audit` | Report the labels a changed set earns from the pull request label map and the paths no row reaches (`--json`) |
57
+ | `aitk census [path]` | Report tracked file count, a breakdown by extension, and a line total that skips whatever reads as binary (`--json`) |
57
58
  | `aitk audits run` | Run every audit as one set, report per check under one verdict, and compare each count to the recorded baseline (`--json`, `--record`) |
58
59
  | `aitk audits list` | List every audit the set runs, with the corpus each reads and whether it gates (`--json`) |
59
60
  | `aitk capture [source]` | Render HTML capture sources to PNG, toolkit-only and absent from an installed package |
@@ -10,6 +10,7 @@ CLI catalog and invocation rules for agents, split by command domain. Start with
10
10
 
11
11
  - [Audits](audits.md): Running every health check as one set, what the single verdict means, the exit code each outcome takes, the retained baseline and the delta it reports, and which corpora are kept out of the record
12
12
  - [Capture](capture.md): Rendering committed HTML sources to PNG, what the command asserts about fonts, and why it is toolkit-only
13
+ - [Census](census.md): Tracked-plus-untracked file count, a breakdown by extension, and a line total that skips whatever reads as binary
13
14
  - [Command catalog](commands.md): Every project-level command and every domain subcommand, plus the shape each domain exposes
14
15
  - [Comments](comments.md): Comment density by language and kind, the two structural exclusions, and how the degradation sweep finds its vocabulary
15
16
  - [Context audit checks](context-audit-checks.md): What each non-gating check reports, the unit each checkpoint is measured in, the architecture record's length gate and claim coverage, which folders each check reaches, and what moved to the attribute tier
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/aitk",
3
3
  "type": "module",
4
- "version": "3.9.0",
4
+ "version": "3.10.0",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -405,6 +405,23 @@ function commentCounts(record: unknown): Record<string, number> | undefined {
405
405
  return { degradationHits }
406
406
  }
407
407
 
408
+ /**
409
+ * Reads the census's three headline totals, leaving the extension breakdown
410
+ * out. The breakdown is what a caller reads for its own sake, and folding
411
+ * every extension into the retained baseline would grow the recorded key set
412
+ * with every language the tree ever picks up.
413
+ */
414
+ function censusCounts(record: unknown): Record<string, number> | undefined {
415
+ const root = asObject(record)
416
+ if (root === undefined || !Array.isArray(root.byExtension)) return undefined
417
+
418
+ return allOf({
419
+ files: typeof root.files === 'number' ? root.files : undefined,
420
+ skipped: typeof root.skipped === 'number' ? root.skipped : undefined,
421
+ lines: typeof root.lines === 'number' ? root.lines : undefined,
422
+ })
423
+ }
424
+
408
425
  function testOrderCounts(record: unknown): Record<string, number> | undefined {
409
426
  const root = asObject(record)
410
427
  if (root === undefined) return undefined
@@ -597,6 +614,17 @@ export const AUDITS: readonly AuditSpec[] = [
597
614
  ] satisfies RestatedRefusal[],
598
615
  counts: restatedCounts,
599
616
  },
617
+ {
618
+ id: 'census',
619
+ label: 'Codebase census',
620
+ argv: ['census', '--json'],
621
+ // Reports rather than gates. A file count is a measure, not a judgment, so
622
+ // registering it here exists to retain growth against the baseline rather
623
+ // than to fail a push on a number moving.
624
+ gatingExits: [],
625
+ corpus: 'tracked',
626
+ counts: censusCounts,
627
+ },
600
628
  {
601
629
  id: 'deps',
602
630
  label: 'Dependency advisories',
@@ -0,0 +1,113 @@
1
+ import { readFile } from 'node:fs/promises'
2
+ import { extname, join } from 'node:path'
3
+ import { isBinary } from '@/binary'
4
+ import { listRepositoryFiles } from '@/git-files'
5
+
6
+ export type CensusRefusal = 'no-git'
7
+
8
+ export interface ExtensionCount {
9
+ readonly extension: string
10
+ readonly files: number
11
+ readonly lines: number
12
+ }
13
+
14
+ export interface CensusResult {
15
+ readonly kind: 'measured'
16
+ readonly files: number
17
+ /** Binary or unreadable, counted toward `files` and excluded from `lines`. */
18
+ readonly skipped: number
19
+ readonly lines: number
20
+ readonly byExtension: readonly ExtensionCount[]
21
+ }
22
+
23
+ export interface CensusFailure {
24
+ readonly kind: 'refused'
25
+ readonly reason: CensusRefusal
26
+ }
27
+
28
+ /**
29
+ * Open descriptors allowed at once while reading the tree, matching
30
+ * `src/comments/scan.ts`. A single `Promise.all` over every path opens one
31
+ * descriptor per file, which exhausts a default 1024 limit on a large
32
+ * repository and fails the whole census with EMFILE.
33
+ */
34
+ const CONCURRENT_READS = 64
35
+
36
+ interface ReadOutcome {
37
+ readonly path: string
38
+ /** Absent when the read failed: a symlink leaving the tree, or a file git
39
+ * listed and the working tree no longer holds. */
40
+ readonly text?: string
41
+ }
42
+
43
+ async function readOne(root: string, path: string): Promise<ReadOutcome> {
44
+ try {
45
+ return { path, text: await readFile(join(root, path), 'utf8') }
46
+ } catch {
47
+ return { path }
48
+ }
49
+ }
50
+
51
+ function extensionFor(path: string): string {
52
+ const ext = extname(path)
53
+ return ext === '' ? 'no-extension' : ext.slice(1).toLowerCase()
54
+ }
55
+
56
+ /**
57
+ * A trailing newline is the file's terminator rather than an empty final line,
58
+ * which is what keeps a file ending `a\nb\n` at two lines rather than three.
59
+ */
60
+ function countLines(text: string): number {
61
+ if (text === '') return 0
62
+ const lines = text.split('\n')
63
+ return text.endsWith('\n') ? lines.length - 1 : lines.length
64
+ }
65
+
66
+ /**
67
+ * Censuses the tracked-plus-untracked tree under `root`: a file count, a
68
+ * breakdown by extension, and a line total that skips whatever reads as
69
+ * binary.
70
+ *
71
+ * Reads `listRepositoryFiles`, the same corpus the citation check, the
72
+ * markdown corpus, and the secret scan already share, so this is not a
73
+ * fourth definition of what counts.
74
+ */
75
+ export async function census(
76
+ root: string,
77
+ ): Promise<CensusResult | CensusFailure> {
78
+ const listed = await listRepositoryFiles(root)
79
+ if (listed === undefined) return { kind: 'refused', reason: 'no-git' }
80
+
81
+ const totals = new Map<string, { files: number; lines: number }>()
82
+ let skipped = 0
83
+ let lines = 0
84
+
85
+ for (let start = 0; start < listed.length; start += CONCURRENT_READS) {
86
+ const batch = listed.slice(start, start + CONCURRENT_READS)
87
+ const outcomes = await Promise.all(batch.map((path) => readOne(root, path)))
88
+
89
+ for (const outcome of outcomes) {
90
+ const extension = extensionFor(outcome.path)
91
+ const entry = totals.get(extension) ?? { files: 0, lines: 0 }
92
+ entry.files += 1
93
+
94
+ // A read that failed and a file that read as binary are counted the
95
+ // same way: toward `files`, excluded from every line count.
96
+ if (outcome.text === undefined || isBinary(outcome.text)) {
97
+ skipped += 1
98
+ } else {
99
+ const counted = countLines(outcome.text)
100
+ entry.lines += counted
101
+ lines += counted
102
+ }
103
+
104
+ totals.set(extension, entry)
105
+ }
106
+ }
107
+
108
+ const byExtension = [...totals.entries()]
109
+ .map(([extension, counts]) => ({ extension, ...counts }))
110
+ .sort((a, b) => b.files - a.files || a.extension.localeCompare(b.extension))
111
+
112
+ return { kind: 'measured', files: listed.length, skipped, lines, byExtension }
113
+ }
package/src/cli.ts CHANGED
@@ -30,6 +30,7 @@ import { register as audits } from '@/commands/audits'
30
30
  import { register as secrets } from '@/commands/secrets'
31
31
  import { register as deps } from '@/commands/deps'
32
32
  import { register as labels } from '@/commands/labels'
33
+ import { register as census } from '@/commands/census'
33
34
  import { register as upgrade } from '@/commands/upgrade'
34
35
  import { readInstalled, UNKNOWN_LABEL } from '@/version/installed'
35
36
  import { palette } from '@/ui'
@@ -71,6 +72,7 @@ function showHelp(): void {
71
72
  `${GREY}│${NC} secrets [cmd] ${GREY}# Read the shipped tree for credential-shaped values (scan)${NC}`,
72
73
  `${GREY}│${NC} deps [cmd] ${GREY}# Read the resolved dependency set for advisories (audit)${NC}`,
73
74
  `${GREY}│${NC} labels [cmd] ${GREY}# Read a changed set against the pull request label map (audit)${NC}`,
75
+ `${GREY}│${NC} census [path] ${GREY}# Report tracked file count, extension breakdown, and line totals${NC}`,
74
76
  `${GREY}│${NC} audits [cmd] ${GREY}# Run every health check as one set (run, list)${NC}`,
75
77
  `${GREY}│${NC} upgrade ${GREY}# Reinstall the CLI globally with the manager that installed it${NC}`,
76
78
  `${GREY}│${NC}`,
@@ -116,6 +118,7 @@ function showHelp(): void {
116
118
  `${GREY}│${NC} aitk secrets scan --json`,
117
119
  `${GREY}│${NC} aitk deps audit --json`,
118
120
  `${GREY}│${NC} aitk labels audit --json`,
121
+ `${GREY}│${NC} aitk census --json`,
119
122
  `${GREY}│${NC} aitk audits run --json`,
120
123
  `${GREY}│${NC} aitk upgrade --json`,
121
124
  `${GREY}└${NC}`,
@@ -164,6 +167,7 @@ sessions(program)
164
167
  secrets(program)
165
168
  deps(program)
166
169
  labels(program)
170
+ census(program)
167
171
  audits(program)
168
172
  upgrade(program)
169
173
 
@@ -0,0 +1,105 @@
1
+ import { resolve } from 'node:path'
2
+ import type { Command } from 'commander'
3
+ import { census, type CensusRefusal } from '@/census/count'
4
+ import { intro, logInfo, logStep, logWarn, outro, plural } from '@/ui'
5
+
6
+ interface CensusCommandOptions {
7
+ readonly json?: boolean
8
+ }
9
+
10
+ const REFUSALS: Record<CensusRefusal, string> = {
11
+ 'no-git': 'git could not list this tree, so the corpus is unknown.',
12
+ }
13
+
14
+ export function register(program: Command): void {
15
+ program
16
+ .command('census')
17
+ .description(
18
+ 'Report tracked file count, a breakdown by extension, and line totals',
19
+ )
20
+ .argument('[path]', 'Tree to census, defaulting to the current directory')
21
+ .helpOption('-h, --help', 'Show this help message')
22
+ .option('--json', 'Add a machine-readable record on stdout')
23
+ .addHelpText(
24
+ 'after',
25
+ [
26
+ '',
27
+ 'Scope:',
28
+ ' Tracked files plus untracked files git does not ignore, the same',
29
+ ' corpus the citation check, the markdown corpus, and the secret',
30
+ ' scan already read. A line total skips whatever reads as binary.',
31
+ '',
32
+ 'Exit codes:',
33
+ ' 0 the census completed',
34
+ ' 1 refused, with the reason on stderr',
35
+ '',
36
+ 'Examples:',
37
+ ' aitk census',
38
+ ' aitk census src --json',
39
+ '',
40
+ ].join('\n'),
41
+ )
42
+ .action(async (path: string | undefined, opts: CensusCommandOptions) => {
43
+ process.exitCode = await runCensus(path, opts)
44
+ })
45
+ }
46
+
47
+ async function runCensus(
48
+ path: string | undefined,
49
+ opts: CensusCommandOptions,
50
+ ): Promise<number> {
51
+ const root = resolve(path ?? process.cwd())
52
+ const emitJson = opts.json ?? false
53
+
54
+ intro('aitk census')
55
+
56
+ const result = await census(root)
57
+
58
+ if (result.kind === 'refused') {
59
+ logStep('Refused')
60
+ logWarn(REFUSALS[result.reason])
61
+ outro()
62
+
63
+ if (emitJson) {
64
+ process.stdout.write(
65
+ `${JSON.stringify({
66
+ root,
67
+ reason: result.reason,
68
+ message: REFUSALS[result.reason],
69
+ })}\n`,
70
+ )
71
+ }
72
+ return 1
73
+ }
74
+
75
+ logStep('Files')
76
+ logInfo(
77
+ `${plural(result.files, 'file')}, ${result.skipped} skipped as binary or unreadable`,
78
+ )
79
+
80
+ logStep('By extension')
81
+ for (const entry of result.byExtension) {
82
+ logInfo(
83
+ `${entry.extension}: ${plural(entry.files, 'file')}, ${entry.lines} lines`,
84
+ )
85
+ }
86
+
87
+ logStep('Lines')
88
+ logInfo(`${result.lines} counted, skipping binary or unreadable files`)
89
+
90
+ outro()
91
+
92
+ if (emitJson) {
93
+ process.stdout.write(
94
+ `${JSON.stringify({
95
+ root,
96
+ files: result.files,
97
+ skipped: result.skipped,
98
+ lines: result.lines,
99
+ byExtension: result.byExtension,
100
+ })}\n`,
101
+ )
102
+ }
103
+
104
+ return 0
105
+ }
@@ -23,8 +23,6 @@ packages = [
23
23
  "format" = "prettier --write --log-level warn --ignore-path .gitignore --ignore-path .prettierignore . && shfmt --write --indent 2 scripts/"
24
24
  "prepare" = "husky"
25
25
  "check" = "./scripts/verify.sh"
26
- "clean" = "./scripts/clean.sh"
27
- "update" = "./scripts/update.sh"
28
26
 
29
27
  [gitignore]
30
28
  "# System" = [".DS_Store"]
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Overview
4
4
 
5
- The base layer covers every project the toolkit scaffolds, whatever language sits on top. It ships formatting, spelling, shell linting, conventional commits, git hooks, CI, and three maintenance scripts. Every other stack extends it, so a decision made here is one every stack inherits.
5
+ The base layer covers every project the toolkit scaffolds, whatever language sits on top. It ships formatting, spelling, shell linting, conventional commits, git hooks, CI, and one maintenance script. Every other stack extends it, so a decision made here is one every stack inherits.
6
6
 
7
7
  A repository with several language roots layers this stack once at the root and skips it per subtree, since git honors only one `core.hooksPath` and a re-dropped husky silently breaks. `docs/target-projects.md` carries the recipe.
8
8
 
@@ -18,7 +18,7 @@ Golden config files live in `tooling/base/configs/` and are copied into the targ
18
18
  - `.github/workflows/verify.yml`: runs on pull requests targeting `main` and on `workflow_dispatch`.
19
19
  - `.github/pull_request_template.md`: `## Summary`, `## Key Changes`, `## Technical Context`, `## Testing`.
20
20
  - `.vscode/extensions.json` and `.vscode/settings.json`: editor wiring for Prettier, cspell, shfmt, and shellcheck.
21
- - `scripts/verify.sh`, `scripts/clean.sh`, `scripts/update.sh`: the maintenance entry points behind `check`, `clean`, and `update`.
21
+ - `scripts/verify.sh`: the maintenance entry point behind `check`.
22
22
 
23
23
  ## What ships as user-owned seeds
24
24
 
@@ -86,13 +86,13 @@ Sticky negative knowledge. Do not relearn.
86
86
 
87
87
  ## CLI
88
88
 
89
- | Script | What it does |
90
- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
91
- | `bun run check` | Full verification suite via `scripts/verify.sh`. Runs `format` first to auto-fix drifted code, then asserts. Honors `VERIFY_NESTED=true` to suppress timeline boundaries when another script calls it. |
92
- | `bun run check:format` | Asserts prettier and shfmt formatting without writing |
93
- | `bun run check:spell` | Runs cspell across every file, with context on failures |
94
- | `bun run check:shell` | Runs shellcheck at warning severity |
95
- | `bun run format` | Writes prettier and shfmt formatting in place |
96
- | `bun run prepare` | Initializes husky hooks, run automatically on `bun install` |
97
- | `bun run clean` | Removes `node_modules/`, clears the bun cache, reinstalls fresh |
98
- | `bun run update` | Runs `bun update --interactive`, then `verify.sh` with `VERIFY_NESTED=true` |
89
+ | Script | What it does |
90
+ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
91
+ | `bun run check` | Repairs a checkout via `scripts/verify.sh`. Honors `VERIFY_NESTED=true` to suppress timeline boundaries when another script calls it. |
92
+ | `bun run check:format` | Asserts prettier and shfmt formatting without writing |
93
+ | `bun run check:spell` | Runs cspell across every file, with context on failures |
94
+ | `bun run check:shell` | Runs shellcheck at warning severity |
95
+ | `bun run format` | Writes prettier and shfmt formatting in place |
96
+ | `bun run prepare` | Initializes husky hooks, run automatically on `bun install` |
97
+
98
+ `bun run check` repairs a checkout rather than gating one. It runs `format` first to auto-fix drifted code, then asserts only what the formatters could not fix. The pull request workflow calls `check:format`, `check:spell`, and `check:shell` directly, and those three are the gate.
@@ -21,12 +21,10 @@ Owns how the project runs on a developer machine: installing the toolchain, the
21
21
 
22
22
  ## Scripts
23
23
 
24
- | Command | Purpose |
25
- | ---------------- | ---------------------------------------------------- |
26
- | `bun run check` | Full verification. Auto-formats, then asserts clean. |
27
- | `bun run format` | Auto-fix prettier and shfmt formatting. |
28
- | `bun run clean` | Wipe `node_modules/`, clear bun cache, reinstall. |
29
- | `bun run update` | Interactive `bun update` followed by verification. |
24
+ | Command | Purpose |
25
+ | ---------------- | ------------------------------------------------------------------------------ |
26
+ | `bun run check` | Repairs locally. Auto-formats, then asserts what the formatters could not fix. |
27
+ | `bun run format` | Auto-fix prettier and shfmt formatting. |
30
28
 
31
29
  ## Shell scripts
32
30
 
@@ -1,45 +0,0 @@
1
- #!/bin/bash
2
- set -e
3
- set -o pipefail
4
-
5
- GREEN='\033[0;32m'
6
- RED='\033[0;31m'
7
- WHITE='\033[1;37m'
8
- GREY='\033[0;90m'
9
- NC='\033[0m'
10
-
11
- log_info() { echo -e "${GREY}│${NC} ${GREEN}✓${NC} $1"; }
12
- log_error() {
13
- echo -e "${GREY}│${NC} ${RED}✗${NC} $1"
14
- exit 1
15
- }
16
- log_step() { echo -e "${GREY}│${NC}\n${GREY}├${NC} ${WHITE}$1${NC}"; }
17
- log_rem() { echo -e "${GREY}│${NC} ${RED}-${NC} $1"; }
18
-
19
- check_dependencies() {
20
- command -v bun >/dev/null 2>&1 || log_error "bun is not installed"
21
- }
22
-
23
- main() {
24
- check_dependencies
25
-
26
- echo -e "${GREY}┌${NC}"
27
-
28
- echo -e "${GREY}├${NC} ${WHITE}Cleaning artifacts${NC}"
29
- rm -rf node_modules
30
-
31
- log_rem "node_modules/"
32
-
33
- log_step "Cleaning cache"
34
- bun pm cache rm
35
- log_info "Package manager cache cleared"
36
-
37
- log_step "Rehydrating project"
38
- bun install
39
- log_info "Dependencies installed"
40
-
41
- echo -e "${GREY}└${NC}\n"
42
- echo -e "${GREEN}✓ Clean complete${NC}"
43
- }
44
-
45
- main "$@"
@@ -1,49 +0,0 @@
1
- #!/bin/bash
2
- set -e
3
- set -o pipefail
4
-
5
- GREEN='\033[0;32m'
6
- YELLOW='\033[0;33m'
7
- RED='\033[0;31m'
8
- WHITE='\033[1;37m'
9
- GREY='\033[0;90m'
10
- NC='\033[0m'
11
-
12
- log_info() { echo -e "${GREY}│${NC} ${GREEN}✓${NC} $1"; }
13
- log_warn() { echo -e "${GREY}│${NC} ${YELLOW}!${NC} $1"; }
14
- log_error() {
15
- echo -e "${GREY}│${NC} ${RED}✗${NC} $1"
16
- exit 1
17
- }
18
- log_step() { echo -e "${GREY}│${NC}\n${GREY}├${NC} ${WHITE}$1${NC}"; }
19
-
20
- pipe_output() { while IFS= read -r line; do echo -e "${GREY}│${NC} $line"; done; }
21
-
22
- check_dependencies() {
23
- command -v bun >/dev/null 2>&1 || log_error "bun is not installed"
24
- }
25
-
26
- main() {
27
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
28
-
29
- check_dependencies
30
-
31
- echo -e "${GREY}┌${NC}"
32
-
33
- echo -e "${GREY}├${NC} ${WHITE}Interactive dependency update${NC}"
34
- echo -e "${GREY}│${NC}"
35
- bun update --interactive
36
-
37
- log_step "Verifying project health"
38
- if [ -f "$SCRIPT_DIR/verify.sh" ]; then
39
- VERIFY_NESTED=true "$SCRIPT_DIR/verify.sh"
40
- log_info "All checks passed"
41
- else
42
- log_warn "Verification script not found, skipping."
43
- fi
44
-
45
- echo -e "${GREY}└${NC}\n"
46
- echo -e "${GREEN}✓ Update complete${NC}"
47
- }
48
-
49
- main "$@"