github-portfolio-analyzer 1.1.0 → 1.2.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/CHANGELOG.md CHANGED
@@ -12,6 +12,19 @@ All notable changes to this project will be documented in this file.
12
12
  - Fallback count in analyze summary when structural inspection fails
13
13
  - Fatal error messages for missing token, auth failure, and rate limit
14
14
 
15
+ ## [1.2.0] — 2026-04-02
16
+
17
+ ### Added
18
+ - `inferRepoCategory()` em `taxonomy.js`: inferência de categoria por heurística de nome, descrição e topics — detecta content, learning, template, library, infra, experiment, product; fallback: tooling
19
+ - `CATEGORY_WEIGHTS` em `scoring.js`: pesos de scoring distintos por categoria — `hasLicense` e `hasTests` zerados para content/learning/experiment, baselines altos para experiment (45), learning (35), template (30) e content (25)
20
+ - `category` exposto no output de `buildReportModel` em `report.js` — consumers do report (worker, frontend) agora recebem essa informação
21
+ - `docs/SCORING_MODEL.md`: documentação completa com tabela de pesos, exemplos end-to-end por categoria, e seção para agentes/LLMs
22
+ - Seção de scoring v2 no `AGENT_GUIDE.md`
23
+
24
+ ### Changed
25
+ - `scoreRepository` agora lê `repository.category` para selecionar os pesos corretos; fallback para `tooling` se category ausente ou inválida
26
+ - `sources.category` em `buildRepoTaxonomy` retorna `'inferred'` em vez de `'default'`
27
+
15
28
  ## [1.0.0] — 2026-03-31
16
29
 
17
30
  ### Added
package/README.md CHANGED
@@ -98,14 +98,17 @@ Design goals:
98
98
 
99
99
  ## Installation
100
100
 
101
- Install dependencies and run the CLI locally:
101
+ ```bash
102
+ npm install -g github-portfolio-analyzer
103
+ ```
104
+
105
+ Verify the installation:
102
106
 
103
107
  ```bash
104
- npm install
105
108
  github-portfolio-analyzer --version
106
109
  ```
107
110
 
108
- If the global binary is not available yet, use:
111
+ If the global binary is not available, run directly:
109
112
 
110
113
  ```bash
111
114
  node bin/github-portfolio-analyzer.js --version
@@ -497,7 +500,7 @@ Each `portfolio.json.items[]` entry includes:
497
500
  - `effort`: `xs | s | m | l | xl`
498
501
  - `value`: `low | medium | high | very-high`
499
502
  - `nextAction`: `"<Verb> <target> — Done when: <measurable condition>"`
500
- - `taxonomyMeta`: per-field provenance (`default | user | inferred`)
503
+ - `taxonomyMeta`: per-field provenance (`default | user | inferred`). For repositories, `sources.category` is always `user` (when set manually) or `inferred` (heuristic) — never `default`.
501
504
 
502
505
  `inventory.json.items[]` includes the same taxonomy fields and `taxonomyMeta` for repositories.
503
506
 
@@ -508,50 +511,133 @@ Each `portfolio.json.items[]` entry includes:
508
511
  - `meta` (generatedAt, asOfDate, owner, counts)
509
512
  - `summary` (state counts, top10 by score, now/next/later/park)
510
513
  - `matrix.completionByEffort` (`CL0..CL5` by `xs..xl`)
511
- - `items[]` with decision fields (`completionLevel`, `effortEstimate`, `priorityBand`, `priorityWhy`)
514
+ - `items[]` with decision fields (`completionLevel`, `effortEstimate`, `priorityBand`, `priorityWhy`, `category`)
512
515
 
513
516
  ## Decision Model (Report)
514
517
 
518
+ Every repository passes through a deterministic scoring pipeline:
519
+
520
+ ```mermaid
521
+ flowchart LR
522
+ subgraph top [ ]
523
+ direction LR
524
+ A([repo metadata]) --> B(inferRepoCategory) --> C([category]) --> D(scoreRepository) --> E([score 0–100])
525
+ end
526
+ subgraph mid [ ]
527
+ direction RL
528
+ J(computePriorityBand) <-- I([effort xs–xl]) <-- H(computeEffortEstimate) <-- G([CL 0–5]) <-- F(computeCompletionLevel)
529
+ end
530
+ E --> F
531
+ E -. feeds .-> J
532
+ G -. feeds .-> J
533
+ J --> park([park]) & later([later]) & next([next]) & now([now])
534
+ ```
535
+
536
+ ### Score
537
+
538
+ Each repository receives a score from 0 to 100 based on observable signals.
539
+ Signal weights depend on the project's **category**, inferred automatically
540
+ from its name, description, and GitHub topics.
541
+
542
+ | Signal | product | tooling | library | content | learning | infra | experiment | template |
543
+ |---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
544
+ | baseline | — | — | — | **25** | **35** | — | **45** | **30** |
545
+ | pushed (90d) | 25 | 25 | 20 | 25 | 20 | 25 | 20 | 10 |
546
+ | README | 15 | 15 | 20 | 15 | 15 | 20 | 15 | **25** |
547
+ | license | 10 | 10 | **20** | ✗ | ✗ | 10 | ✗ | 10 |
548
+ | tests | 25 | 20 | **25** | ✗ | ✗ | 10 | ✗ | 5 |
549
+ | stars > 1 | 5 | 5 | 10 | 5 | 5 | 5 | 5 | 10 |
550
+ | updated (180d) | 20 | 25 | 5 | **30** | **25** | **30** | 15 | 10 |
551
+
552
+ `✗` = irrelevant for this category (weight 0). `library` penalizes missing
553
+ license most heavily. `experiment` and `learning` skip tests and license entirely.
554
+
555
+ Example — a `content` repo with no license and no tests still scores 95:
556
+
557
+ ```
558
+ "prompt-library" category: content
559
+ ────────────────────────────────────
560
+ baseline +25
561
+ pushed 10d ago +25
562
+ has README +15
563
+ has license +0 (irrelevant for content)
564
+ has tests +0 (irrelevant for content)
565
+ updated this month +30
566
+ ────────────────────────────────────
567
+ score 95
568
+ ```
569
+
570
+ See [docs/SCORING_MODEL.md](docs/SCORING_MODEL.md) for the full weight table
571
+ and numeric examples for every category.
572
+
515
573
  ### Completion Level
516
574
 
517
- - `CL0`: no README
518
- - `CL1`: has README
519
- - `CL2`: has package.json, or non-JS repo with size >= 500 KB
520
- - `CL3`: CL2 + CI
521
- - `CL4`: CL3 + tests
522
- - `CL5`: CL4 + score >= 70
523
- - Ideas default to `CL0`
575
+ Reflects structural maturity, regardless of category. Ideas always default to CL 0.
524
576
 
525
- ### Effort Estimate
577
+ | CL | Label | Condition |
578
+ |---|---|---|
579
+ | 0 | Concept only | no README, or `type: idea` |
580
+ | 1 | Documented | has README |
581
+ | 2 | Structured baseline | has `package.json` (or non-JS repo ≥ 500 KB) |
582
+ | 3 | Automated workflow | CL 2 + CI |
583
+ | 4 | Tested workflow | CL 3 + tests |
584
+ | 5 | Production-ready candidate | CL 4 + score ≥ 70 |
526
585
 
527
- Uses taxonomy `effort` unless `effort` source is `default`.
528
- If defaulted, infer by size and completion:
586
+ ### Effort Estimate
529
587
 
530
- - `xs`: size < 100 KB and CL <= 2
531
- - `s`: size < 500 KB and CL <= 3
532
- - `m`: size < 5000 KB
533
- - `l`: size < 20000 KB
534
- - `xl`: size >= 20000 KB
588
+ How much work remains to bring a project to its next meaningful state.
589
+ Inferred automatically from repository size and completion level when not set manually.
590
+ `effortEstimate` is a report-only field it never overwrites the taxonomy `effort`.
535
591
 
536
- `effortEstimate` is a report field only; it does not overwrite taxonomy `effort`.
592
+ | Estimate | Size | CL | What it means |
593
+ |---|---|---|---|
594
+ | `xs` | < 100 KB | ≤ 2 | A few hours. Easy to restart from scratch. |
595
+ | `s` | < 500 KB | ≤ 3 | A day or two. Focused sprint. |
596
+ | `m` | < 5 MB | any | About a week. Needs planning. |
597
+ | `l` | < 20 MB | any | Multiple weeks. Real commitment required. |
598
+ | `xl` | ≥ 20 MB | any | A long-term project. Strategic investment. |
537
599
 
538
600
  ### Priority Band
539
601
 
540
- Internal score calculation:
602
+ The base score is adjusted by state, completion, and effort to produce a
603
+ final `priorityScore`, which determines the band.
541
604
 
542
- - base: `score`
543
- - `+10` if state `active`
544
- - `+5` if state `stale`
545
- - `-20` if state `abandoned` or `archived`
546
- - `+10` if completion is CL1..CL3
547
- - `-10` if effortEstimate is `l` or `xl`
605
+ | Modifier | Condition | Effect |
606
+ |---|---|---|
607
+ | State boost | `active` | +10 |
608
+ | State boost | `stale` | +5 |
609
+ | State penalty | `abandoned` or `archived` | −20 |
610
+ | Quick-win boost | CL 1, 2, or 3 | +10 |
611
+ | Effort penalty | `l` or `xl` | −10 |
612
+
613
+ `priorityScore` has no lower bound — it can go negative.
614
+
615
+ | Band | Range | Meaning |
616
+ |---|---|---|
617
+ | `park` | < 45 | Needs a decision before any investment. Abandoned, low signal, or intentionally paused. |
618
+ | `later` | 45–64 | Viable but not urgent. Can return when backlog has room. |
619
+ | `next` | 65–79 | Strong candidate. High score but large effort, or active with average score. |
620
+ | `now` | ≥ 80 | High confidence. Active project, good score, low effort — or manually pinned. |
548
621
 
549
- Band mapping:
622
+ Example — modifiers can push a `park`-bound project below zero:
550
623
 
551
- - `now`: >= 80
552
- - `next`: 65..79
553
- - `later`: 45..64
554
- - `park`: < 45
624
+ ```
625
+ "old-monolith" category: product
626
+ ──────────────────────────────────
627
+ baseline 0
628
+ pushed 400d ago +0 (> 90 days)
629
+ has README +15
630
+ has license +10
631
+ no tests +0
632
+ updated 200d ago +0 (> 180 days)
633
+ ──────────────────────────────────
634
+ score 25
635
+
636
+ state=abandoned −20
637
+ effort=xl −10
638
+ ──────────────────────────────────
639
+ priorityScore −5 → park
640
+ ```
555
641
 
556
642
  ## Determinism and Time Rules
557
643
 
@@ -654,10 +740,13 @@ npm test
654
740
  Coverage includes:
655
741
 
656
742
  - activity/maturity/scoring boundaries
743
+ - category inference from repository name, description, and topics
744
+ - category-aware scoring weights and category preservation for user-specified values
657
745
  - taxonomy presence and provenance behavior
658
746
  - `nextAction` validation and normalization
659
747
  - portfolio merge determinism
660
748
  - report completion logic, priority mapping, and deterministic model generation
749
+ - `category` propagation to report items and all summary bands
661
750
 
662
751
  ## Troubleshooting
663
752
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "github-portfolio-analyzer",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "commands": [
5
5
  { "id": "analyze" },
6
6
  { "id": "ingest-ideas" },
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "github-portfolio-analyzer",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "CLI tool to analyze GitHub repos and portfolio ideas",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,7 +17,7 @@
17
17
  },
18
18
  "repository": {
19
19
  "type": "git",
20
- "url": "https://github.com/paulo-raoni/github-portfolio-analyzer.git"
20
+ "url": "git+https://github.com/paulo-raoni/github-portfolio-analyzer.git"
21
21
  },
22
22
  "files": [
23
23
  "bin/",
@@ -176,7 +176,11 @@
176
176
  "type": "array",
177
177
  "items": { "type": "string" }
178
178
  },
179
- "nextAction": { "type": "string" }
179
+ "nextAction": { "type": "string" },
180
+ "category": {
181
+ "type": "string",
182
+ "enum": ["product", "tooling", "library", "learning", "content", "infra", "experiment", "template"]
183
+ }
180
184
  }
181
185
  },
182
186
  "item": {
@@ -244,6 +248,10 @@
244
248
  },
245
249
  "htmlUrl": { "type": "string" },
246
250
  "homepage": { "type": "string" },
251
+ "category": {
252
+ "type": "string",
253
+ "enum": ["product", "tooling", "library", "learning", "content", "infra", "experiment", "template"]
254
+ },
247
255
  "presentationState": {
248
256
  "type": "string",
249
257
  "enum": ["featured", "complete", "in-progress", "salvageable", "learning", "archived", "hidden"]
package/src/cli.js CHANGED
File without changes
@@ -73,17 +73,22 @@ export async function runAnalyzeCommand(options = {}) {
73
73
  const structuralHealth = await inspectRepositoryStructure(github, normalized);
74
74
  const activity = classifyActivity(normalized._pushedAt, asOfDate);
75
75
  const maturity = classifyMaturity(normalized.sizeKb);
76
- const { score, scoreBreakdown } = scoreRepository(
77
- { ...normalized, structuralHealth, pushedAt: normalized._pushedAt, updatedAt: normalized._updatedAt },
78
- asOfDate
79
- );
80
76
  const taxonomy = buildRepoTaxonomy({
81
77
  ...normalized,
82
78
  structuralHealth,
83
79
  activity,
84
- maturity,
85
- score
80
+ maturity
86
81
  });
82
+ const { score, scoreBreakdown } = scoreRepository(
83
+ {
84
+ ...normalized,
85
+ structuralHealth,
86
+ pushedAt: normalized._pushedAt,
87
+ updatedAt: normalized._updatedAt,
88
+ category: taxonomy.category
89
+ },
90
+ asOfDate
91
+ );
87
92
 
88
93
  return stripInternalFields({
89
94
  ...normalized,
@@ -106,22 +111,22 @@ export async function runAnalyzeCommand(options = {}) {
106
111
  hasTests: false,
107
112
  hasCi: false
108
113
  };
114
+ const taxonomy = buildRepoTaxonomy({
115
+ ...normalized,
116
+ structuralHealth: fallbackStructuralHealth,
117
+ activity,
118
+ maturity
119
+ });
109
120
  const { score, scoreBreakdown } = scoreRepository(
110
121
  {
111
122
  ...normalized,
112
123
  structuralHealth: fallbackStructuralHealth,
113
124
  pushedAt: normalized._pushedAt,
114
- updatedAt: normalized._updatedAt
125
+ updatedAt: normalized._updatedAt,
126
+ category: taxonomy.category
115
127
  },
116
128
  asOfDate
117
129
  );
118
- const taxonomy = buildRepoTaxonomy({
119
- ...normalized,
120
- structuralHealth: fallbackStructuralHealth,
121
- activity,
122
- maturity,
123
- score
124
- });
125
130
 
126
131
  return stripInternalFields({
127
132
  ...normalized,
File without changes
File without changes
File without changes
package/src/config.js CHANGED
File without changes
File without changes
package/src/core/ideas.js CHANGED
File without changes
File without changes
File without changes
@@ -200,7 +200,8 @@ export function buildReportModel(portfolioData, inventoryData = null, options =
200
200
  ...(item.language != null ? { language: item.language } : {}),
201
201
  ...(Array.isArray(item.topics) && item.topics.length > 0 ? { topics: item.topics } : {}),
202
202
  ...(item.htmlUrl != null ? { htmlUrl: item.htmlUrl } : {}),
203
- ...(item.homepage != null ? { homepage: item.homepage } : {})
203
+ ...(item.homepage != null ? { homepage: item.homepage } : {}),
204
+ ...(item.category != null ? { category: item.category } : {})
204
205
  };
205
206
  });
206
207
 
@@ -362,7 +363,8 @@ function toSummaryItem(item) {
362
363
  ...(item.priorityTag ? { priorityTag: item.priorityTag } : {}),
363
364
  priorityOverrides: item.priorityOverrides,
364
365
  priorityWhy: item.priorityWhy,
365
- nextAction: item.nextAction
366
+ nextAction: item.nextAction,
367
+ ...(item.category != null ? { category: item.category } : {})
366
368
  };
367
369
  }
368
370
 
@@ -1,8 +1,49 @@
1
1
  import { daysSince } from './classification.js';
2
2
 
3
+ const CATEGORY_WEIGHTS = {
4
+ product: {
5
+ pushedWithin90Days: 25, hasReadme: 15, hasLicense: 10,
6
+ hasTests: 25, starsOverOne: 5, updatedWithin180Days: 20, baseline: 0
7
+ },
8
+ tooling: {
9
+ pushedWithin90Days: 25, hasReadme: 15, hasLicense: 10,
10
+ hasTests: 20, starsOverOne: 5, updatedWithin180Days: 25, baseline: 0
11
+ },
12
+ library: {
13
+ pushedWithin90Days: 20, hasReadme: 20, hasLicense: 20,
14
+ hasTests: 25, starsOverOne: 10, updatedWithin180Days: 5, baseline: 0
15
+ },
16
+ content: {
17
+ pushedWithin90Days: 25, hasReadme: 15, hasLicense: 0,
18
+ hasTests: 0, starsOverOne: 5, updatedWithin180Days: 30, baseline: 25
19
+ },
20
+ learning: {
21
+ pushedWithin90Days: 20, hasReadme: 15, hasLicense: 0,
22
+ hasTests: 0, starsOverOne: 5, updatedWithin180Days: 25, baseline: 35
23
+ },
24
+ infra: {
25
+ pushedWithin90Days: 25, hasReadme: 20, hasLicense: 10,
26
+ hasTests: 10, starsOverOne: 5, updatedWithin180Days: 30, baseline: 0
27
+ },
28
+ experiment: {
29
+ pushedWithin90Days: 20, hasReadme: 15, hasLicense: 0,
30
+ hasTests: 0, starsOverOne: 5, updatedWithin180Days: 15, baseline: 45
31
+ },
32
+ template: {
33
+ pushedWithin90Days: 10, hasReadme: 25, hasLicense: 10,
34
+ hasTests: 5, starsOverOne: 10, updatedWithin180Days: 10, baseline: 30
35
+ }
36
+ };
37
+
38
+ const DEFAULT_WEIGHTS = CATEGORY_WEIGHTS.tooling;
39
+
3
40
  export function scoreRepository(repository, asOfDate) {
4
- let score = 0;
41
+ const category = repository.category ?? 'tooling';
42
+ const weights = CATEGORY_WEIGHTS[category] ?? DEFAULT_WEIGHTS;
43
+
44
+ let score = weights.baseline ?? 0;
5
45
  const breakdown = {
46
+ baseline: weights.baseline ?? 0,
6
47
  pushedWithin90Days: 0,
7
48
  hasReadme: 0,
8
49
  hasLicense: 0,
@@ -11,34 +52,34 @@ export function scoreRepository(repository, asOfDate) {
11
52
  updatedWithin180Days: 0
12
53
  };
13
54
 
14
- if (daysSince(repository.pushedAt, asOfDate) <= 90) {
15
- score += 30;
16
- breakdown.pushedWithin90Days = 30;
55
+ if (weights.pushedWithin90Days > 0 && daysSince(repository.pushedAt, asOfDate) <= 90) {
56
+ score += weights.pushedWithin90Days;
57
+ breakdown.pushedWithin90Days = weights.pushedWithin90Days;
17
58
  }
18
59
 
19
- if (repository.structuralHealth?.hasReadme) {
20
- score += 15;
21
- breakdown.hasReadme = 15;
60
+ if (weights.hasReadme > 0 && repository.structuralHealth?.hasReadme) {
61
+ score += weights.hasReadme;
62
+ breakdown.hasReadme = weights.hasReadme;
22
63
  }
23
64
 
24
- if (repository.structuralHealth?.hasLicense) {
25
- score += 10;
26
- breakdown.hasLicense = 10;
65
+ if (weights.hasLicense > 0 && repository.structuralHealth?.hasLicense) {
66
+ score += weights.hasLicense;
67
+ breakdown.hasLicense = weights.hasLicense;
27
68
  }
28
69
 
29
- if (repository.structuralHealth?.hasTests) {
30
- score += 20;
31
- breakdown.hasTests = 20;
70
+ if (weights.hasTests > 0 && repository.structuralHealth?.hasTests) {
71
+ score += weights.hasTests;
72
+ breakdown.hasTests = weights.hasTests;
32
73
  }
33
74
 
34
- if ((repository.stargazersCount ?? 0) > 1) {
35
- score += 5;
36
- breakdown.starsOverOne = 5;
75
+ if (weights.starsOverOne > 0 && (repository.stargazersCount ?? 0) > 1) {
76
+ score += weights.starsOverOne;
77
+ breakdown.starsOverOne = weights.starsOverOne;
37
78
  }
38
79
 
39
- if (daysSince(repository.updatedAt, asOfDate) <= 180) {
40
- score += 20;
41
- breakdown.updatedWithin180Days = 20;
80
+ if (weights.updatedWithin180Days > 0 && daysSince(repository.updatedAt, asOfDate) <= 180) {
81
+ score += weights.updatedWithin180Days;
82
+ breakdown.updatedWithin180Days = weights.updatedWithin180Days;
42
83
  }
43
84
 
44
85
  return {
@@ -1,17 +1,57 @@
1
1
  import { formatNextAction, normalizeNextAction } from '../utils/nextAction.js';
2
2
 
3
+ function inferRepoCategory(repository) {
4
+ const name = String(repository.name ?? '').toLowerCase();
5
+ const desc = String(repository.description ?? '').toLowerCase();
6
+ const topics = Array.isArray(repository.topics)
7
+ ? repository.topics.map((topic) => String(topic).toLowerCase())
8
+ : [];
9
+ const all = [name, desc, ...topics].join(' ');
10
+
11
+ if (/\b(prompt|note|notes|snippet|snippets|cheatsheet|doc|docs|documentation|knowledge|wiki|resource|resources|writing|content|guide|guides|cookbook)\b/.test(all)) {
12
+ return 'content';
13
+ }
14
+
15
+ if (/\b(learn|learning|study|exercise|exercises|course|tutorial|tutorials|practice|training|bootcamp|challenge|challenges|kata)\b/.test(all)) {
16
+ return 'learning';
17
+ }
18
+
19
+ if (/\b(template|templates|boilerplate|starter|scaffold|skeleton|seed|base|init)\b/.test(all)) {
20
+ return 'template';
21
+ }
22
+
23
+ if (/\b(lib|library|sdk|package|npm|module|plugin|extension|addon|util|utils|helper|helpers)\b/.test(all)) {
24
+ return 'library';
25
+ }
26
+
27
+ if (/\b(infra|infrastructure|docker|kubernetes|k8s|ci|cd|pipeline|deploy|devops|ansible|terraform|nginx|proxy)\b/.test(all)) {
28
+ return 'infra';
29
+ }
30
+
31
+ if (/\b(poc|proof|experiment|spike|demo|prototype|sandbox|playground|try|trying)\b/.test(all)) {
32
+ return 'experiment';
33
+ }
34
+
35
+ if (/\b(app|application|system|platform|service|api|backend|frontend|web|mobile|dashboard|portal|saas)\b/.test(all)) {
36
+ return 'product';
37
+ }
38
+
39
+ return 'tooling';
40
+ }
41
+
3
42
  export function buildRepoTaxonomy(repository) {
4
43
  const activityState = repository.activity;
5
44
  const state = repository.archived ? 'archived' : normalizeState(activityState, 'active');
6
45
 
7
- const category = 'tooling';
46
+ const userCategory = normalizeCategory(repository.category);
47
+ const category = userCategory ?? inferRepoCategory(repository);
8
48
  const strategy = 'maintenance';
9
49
  const effort = 'm';
10
50
  const value = 'medium';
11
51
  const nextAction = defaultRepoNextAction(state);
12
52
 
13
53
  const sources = {
14
- category: 'default',
54
+ category: userCategory ? 'user' : 'inferred',
15
55
  state: repository.archived ? 'inferred' : 'inferred',
16
56
  strategy: 'default',
17
57
  effort: 'default',
package/src/errors.js CHANGED
File without changes
File without changes
File without changes
File without changes
package/src/io/csv.js CHANGED
File without changes
package/src/io/files.js CHANGED
File without changes
File without changes
package/src/io/report.js CHANGED
@@ -108,7 +108,8 @@ function renderAsciiBandSection(title, items) {
108
108
  }
109
109
 
110
110
  items.slice(0, 5).forEach((item, index) => {
111
- lines.push(`${index + 1}) ${item.slug} Score ${item.score} CL${item.completionLevel} — Effort ${item.effortEstimate} — State ${item.state}`);
111
+ const categoryPrefix = item.category == null ? '' : `[${item.category}] `;
112
+ lines.push(`${index + 1}) ${categoryPrefix}${item.slug} — Score ${item.score} — CL${item.completionLevel} — Effort ${item.effortEstimate} — State ${item.state}`);
112
113
  lines.push(` Why: ${item.priorityWhy?.join('; ') ?? ''}`);
113
114
  lines.push(` Next: ${item.nextAction}`);
114
115
  });
@@ -129,7 +130,8 @@ function renderMarkdownBandSection(title, items) {
129
130
  }
130
131
 
131
132
  items.slice(0, 5).forEach((item, index) => {
132
- lines.push(`${index + 1}. **${item.slug}** Score ${item.score} CL${item.completionLevel} — Effort ${item.effortEstimate} — State ${item.state}`);
133
+ const categoryPrefix = item.category == null ? '' : `\`${item.category}\` `;
134
+ lines.push(`${index + 1}. ${categoryPrefix}**${item.slug}** — Score ${item.score} — CL${item.completionLevel} — Effort ${item.effortEstimate} — State ${item.state}`);
133
135
  lines.push(` - Why: ${item.priorityWhy?.join('; ') ?? ''}`);
134
136
  lines.push(` - Next: ${item.nextAction}`);
135
137
  });
package/src/utils/args.js CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/src/utils/slug.js CHANGED
File without changes
package/src/utils/time.js CHANGED
File without changes