knowns 0.1.4 → 0.1.6
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 +43 -0
- package/CLAUDE.md +525 -96
- package/README.md +118 -472
- package/dist/index.js +1039 -353
- package/dist/mcp/server.js +7 -2
- package/dist/ui/assets/index-B6wqA_A-.js +152 -0
- package/dist/ui/assets/index-uFwTg_PH.css +1 -0
- package/dist/ui/index.html +2 -1
- package/package.json +16 -22
- package/dist/ui/index.css +0 -3091
- package/dist/ui/main.css +0 -1
- package/dist/ui/main.js +0 -452
package/dist/index.js
CHANGED
|
@@ -10489,10 +10489,10 @@ function serializeTaskMarkdown(task) {
|
|
|
10489
10489
|
title: task.title,
|
|
10490
10490
|
status: task.status,
|
|
10491
10491
|
priority: task.priority,
|
|
10492
|
-
labels: task.labels,
|
|
10492
|
+
labels: task.labels || [],
|
|
10493
10493
|
createdAt: task.createdAt.toISOString(),
|
|
10494
10494
|
updatedAt: task.updatedAt.toISOString(),
|
|
10495
|
-
timeSpent: task.timeSpent
|
|
10495
|
+
timeSpent: task.timeSpent ?? 0
|
|
10496
10496
|
};
|
|
10497
10497
|
if (task.assignee)
|
|
10498
10498
|
frontmatter.assignee = task.assignee;
|
|
@@ -10743,6 +10743,11 @@ class FileStore {
|
|
|
10743
10743
|
const task = {
|
|
10744
10744
|
...taskData,
|
|
10745
10745
|
id,
|
|
10746
|
+
labels: taskData.labels || [],
|
|
10747
|
+
subtasks: taskData.subtasks || [],
|
|
10748
|
+
acceptanceCriteria: taskData.acceptanceCriteria || [],
|
|
10749
|
+
timeSpent: taskData.timeSpent ?? 0,
|
|
10750
|
+
timeEntries: taskData.timeEntries || [],
|
|
10746
10751
|
createdAt: now,
|
|
10747
10752
|
updatedAt: now
|
|
10748
10753
|
};
|
|
@@ -11447,12 +11452,165 @@ var import_prompts = __toESM(require_prompts3(), 1);
|
|
|
11447
11452
|
var KNOWNS_GUIDELINES = `<!-- KNOWNS GUIDELINES START -->
|
|
11448
11453
|
# Knowns CLI Guidelines
|
|
11449
11454
|
|
|
11450
|
-
## Core
|
|
11455
|
+
## Core Principles
|
|
11451
11456
|
|
|
11457
|
+
### 1. CLI-Only Operations
|
|
11452
11458
|
**NEVER edit .md files directly. ALL operations MUST use CLI commands.**
|
|
11453
11459
|
|
|
11454
11460
|
This ensures data integrity, maintains proper change history, and prevents file corruption.
|
|
11455
11461
|
|
|
11462
|
+
### 2. Documentation-First (For AI Agents)
|
|
11463
|
+
**ALWAYS read project documentation BEFORE planning or coding.**
|
|
11464
|
+
|
|
11465
|
+
AI agents must understand project context, conventions, and existing patterns before making any changes. This prevents rework and ensures consistency.
|
|
11466
|
+
|
|
11467
|
+
---
|
|
11468
|
+
|
|
11469
|
+
## AI Agent Guidelines
|
|
11470
|
+
|
|
11471
|
+
> **CRITICAL**: Before performing ANY task, AI agents MUST read documentation to understand project context.
|
|
11472
|
+
|
|
11473
|
+
### First-Time Initialization
|
|
11474
|
+
|
|
11475
|
+
When starting a new session or working on an unfamiliar project:
|
|
11476
|
+
|
|
11477
|
+
\`\`\`bash
|
|
11478
|
+
# 1. List all available documentation
|
|
11479
|
+
knowns doc list --plain
|
|
11480
|
+
|
|
11481
|
+
# 2. Read essential project docs (prioritize these)
|
|
11482
|
+
knowns doc view "README" --plain # Project overview
|
|
11483
|
+
knowns doc view "ARCHITECTURE" --plain # System design
|
|
11484
|
+
knowns doc view "CONVENTIONS" --plain # Coding standards
|
|
11485
|
+
knowns doc view "API" --plain # API specifications
|
|
11486
|
+
|
|
11487
|
+
# 3. Review current task backlog
|
|
11488
|
+
knowns task list --plain
|
|
11489
|
+
knowns task list --status in-progress --plain
|
|
11490
|
+
\`\`\`
|
|
11491
|
+
|
|
11492
|
+
### Before Taking Any Task
|
|
11493
|
+
|
|
11494
|
+
\`\`\`bash
|
|
11495
|
+
# 1. View the task details
|
|
11496
|
+
knowns task view <id> --plain
|
|
11497
|
+
|
|
11498
|
+
# 2. Follow ALL refs in the task (see Reference System section)
|
|
11499
|
+
# @.knowns/tasks/task-44 - ... \u2192 knowns task view 44 --plain
|
|
11500
|
+
# @.knowns/docs/patterns/module.md \u2192 knowns doc view "patterns/module" --plain
|
|
11501
|
+
|
|
11502
|
+
# 3. Search for additional related documentation
|
|
11503
|
+
knowns search "<keywords from task>" --type doc --plain
|
|
11504
|
+
|
|
11505
|
+
# 4. Read ALL related docs before planning
|
|
11506
|
+
knowns doc view "<related-doc>" --plain
|
|
11507
|
+
|
|
11508
|
+
# 5. Check for similar completed tasks (learn from history)
|
|
11509
|
+
knowns search "<keywords>" --type task --status done --plain
|
|
11510
|
+
\`\`\`
|
|
11511
|
+
|
|
11512
|
+
### Why Documentation First?
|
|
11513
|
+
|
|
11514
|
+
| Without Reading Docs | With Reading Docs |
|
|
11515
|
+
|---------------------|-------------------|
|
|
11516
|
+
| Reinvent existing patterns | Reuse established patterns |
|
|
11517
|
+
| Break conventions | Follow project standards |
|
|
11518
|
+
| Duplicate code | Use existing utilities |
|
|
11519
|
+
| Wrong architecture decisions | Align with system design |
|
|
11520
|
+
| Inconsistent naming | Match naming conventions |
|
|
11521
|
+
|
|
11522
|
+
### Context Checklist for Agents
|
|
11523
|
+
|
|
11524
|
+
Before writing ANY code, ensure you can answer:
|
|
11525
|
+
|
|
11526
|
+
- [ ] Have I followed ALL refs (\`@.knowns/...\`) in the task?
|
|
11527
|
+
- [ ] Have I followed nested refs recursively?
|
|
11528
|
+
- [ ] What is the project's overall architecture?
|
|
11529
|
+
- [ ] What coding conventions does this project follow?
|
|
11530
|
+
- [ ] Are there existing patterns/utilities I should reuse?
|
|
11531
|
+
- [ ] What are the testing requirements?
|
|
11532
|
+
- [ ] How should I structure my implementation?
|
|
11533
|
+
|
|
11534
|
+
> **Remember**: A few minutes reading docs saves hours of rework. NEVER skip this step.
|
|
11535
|
+
|
|
11536
|
+
---
|
|
11537
|
+
|
|
11538
|
+
## Reference System (Refs)
|
|
11539
|
+
|
|
11540
|
+
Tasks and docs can contain **references** to other tasks/docs. AI agents MUST understand and follow these refs to gather complete context.
|
|
11541
|
+
|
|
11542
|
+
### Reference Formats
|
|
11543
|
+
|
|
11544
|
+
| Type | User Input | System Output | CLI Command |
|
|
11545
|
+
|------|------------|---------------|-------------|
|
|
11546
|
+
| **Task ref** | \`@task-<id>\` | \`@.knowns/tasks/task-<id> - <title>.md\` | \`knowns task view <id> --plain\` |
|
|
11547
|
+
| **Doc ref** | \`@doc/<path>\` | \`@.knowns/docs/<path>.md\` | \`knowns doc view "<path>" --plain\` |
|
|
11548
|
+
|
|
11549
|
+
### How to Follow Refs
|
|
11550
|
+
|
|
11551
|
+
When you read a task and see refs in system output format, follow them:
|
|
11552
|
+
|
|
11553
|
+
\`\`\`bash
|
|
11554
|
+
# Example: Task 42 output contains:
|
|
11555
|
+
# @.knowns/tasks/task-44 - CLI Task Create Command.md
|
|
11556
|
+
# @.knowns/docs/patterns/module.md
|
|
11557
|
+
|
|
11558
|
+
# Follow task ref (extract ID from task-<id>)
|
|
11559
|
+
knowns task view 44 --plain
|
|
11560
|
+
|
|
11561
|
+
# Follow doc ref (extract path, remove .md)
|
|
11562
|
+
knowns doc view "patterns/module" --plain
|
|
11563
|
+
\`\`\`
|
|
11564
|
+
|
|
11565
|
+
### Parsing Rules
|
|
11566
|
+
|
|
11567
|
+
1. **Task refs**: \`@.knowns/tasks/task-<id> - ...\` \u2192 extract \`<id>\` \u2192 \`knowns task view <id> --plain\`
|
|
11568
|
+
2. **Doc refs**: \`@.knowns/docs/<path>.md\` \u2192 extract \`<path>\` \u2192 \`knowns doc view "<path>" --plain\`
|
|
11569
|
+
|
|
11570
|
+
### Recursive Following
|
|
11571
|
+
|
|
11572
|
+
Refs can be nested. Follow until complete context is gathered:
|
|
11573
|
+
|
|
11574
|
+
\`\`\`
|
|
11575
|
+
Task 42
|
|
11576
|
+
\u2192 @.knowns/docs/README.md
|
|
11577
|
+
\u2192 @.knowns/docs/patterns/module.md (found in README)
|
|
11578
|
+
\u2192 (read for full pattern details)
|
|
11579
|
+
\`\`\`
|
|
11580
|
+
|
|
11581
|
+
### When to Follow Refs
|
|
11582
|
+
|
|
11583
|
+
| Situation | Action |
|
|
11584
|
+
|-----------|--------|
|
|
11585
|
+
| Refs in Description | ALWAYS follow - critical context |
|
|
11586
|
+
| Refs in Implementation Plan | Follow if implementing related work |
|
|
11587
|
+
| Refs in Notes | Optional - for historical context |
|
|
11588
|
+
| Dependency mentions | Follow if marked as blocker |
|
|
11589
|
+
|
|
11590
|
+
### Example: Complete Ref Resolution
|
|
11591
|
+
|
|
11592
|
+
\`\`\`bash
|
|
11593
|
+
# 1. Read the task
|
|
11594
|
+
$ knowns task view 42 --plain
|
|
11595
|
+
|
|
11596
|
+
# Output contains:
|
|
11597
|
+
# @.knowns/tasks/task-44 - CLI Task Create Command.md
|
|
11598
|
+
# @.knowns/docs/README.md
|
|
11599
|
+
|
|
11600
|
+
# 2. Follow task ref
|
|
11601
|
+
$ knowns task view 44 --plain
|
|
11602
|
+
|
|
11603
|
+
# 3. Follow doc ref
|
|
11604
|
+
$ knowns doc view "README" --plain
|
|
11605
|
+
|
|
11606
|
+
# 4. If README contains more refs, follow them too
|
|
11607
|
+
# @.knowns/docs/patterns/module.md \u2192 knowns doc view "patterns/module" --plain
|
|
11608
|
+
|
|
11609
|
+
# Now you have complete context
|
|
11610
|
+
\`\`\`
|
|
11611
|
+
|
|
11612
|
+
> **CRITICAL**: Never assume you understand a task fully without following its refs. Refs contain essential context that may change your implementation approach.
|
|
11613
|
+
|
|
11456
11614
|
---
|
|
11457
11615
|
|
|
11458
11616
|
## Quick Start
|
|
@@ -11476,63 +11634,195 @@ knowns search "query" --plain
|
|
|
11476
11634
|
|
|
11477
11635
|
---
|
|
11478
11636
|
|
|
11479
|
-
##
|
|
11637
|
+
## End-to-End Example
|
|
11638
|
+
|
|
11639
|
+
Here's a complete workflow for an AI agent implementing a feature:
|
|
11640
|
+
|
|
11641
|
+
\`\`\`bash
|
|
11642
|
+
# === AGENT SESSION START (Do this once per session) ===
|
|
11643
|
+
|
|
11644
|
+
# 0a. List all available documentation
|
|
11645
|
+
$ knowns doc list --plain
|
|
11646
|
+
|
|
11647
|
+
# Output:
|
|
11648
|
+
# DOC: README.md
|
|
11649
|
+
# DOC: ARCHITECTURE.md
|
|
11650
|
+
# DOC: CONVENTIONS.md
|
|
11651
|
+
# DOC: security-patterns.md
|
|
11652
|
+
# DOC: api-guidelines.md
|
|
11653
|
+
# DOC: email-templates.md
|
|
11654
|
+
|
|
11655
|
+
# 0b. Read essential project docs
|
|
11656
|
+
$ knowns doc view "README" --plain
|
|
11657
|
+
$ knowns doc view "ARCHITECTURE" --plain
|
|
11658
|
+
$ knowns doc view "CONVENTIONS" --plain
|
|
11659
|
+
|
|
11660
|
+
# Now the agent understands project context and conventions
|
|
11661
|
+
|
|
11662
|
+
# === TASK WORKFLOW ===
|
|
11663
|
+
|
|
11664
|
+
# 1. Create the task
|
|
11665
|
+
$ knowns task create "Add password reset flow" \\
|
|
11666
|
+
-d "Users need ability to reset forgotten passwords via email" \\
|
|
11667
|
+
--ac "User can request password reset via email" \\
|
|
11668
|
+
--ac "Reset link expires after 1 hour" \\
|
|
11669
|
+
--ac "User can set new password via reset link" \\
|
|
11670
|
+
--ac "Unit tests cover all scenarios" \\
|
|
11671
|
+
--priority high \\
|
|
11672
|
+
-l "auth,feature"
|
|
11673
|
+
|
|
11674
|
+
# Output: Created task AUTH-042
|
|
11675
|
+
|
|
11676
|
+
# 2. Take the task and start timer
|
|
11677
|
+
$ knowns task edit AUTH-042 -s in-progress -a @me
|
|
11678
|
+
$ knowns time start AUTH-042
|
|
11679
|
+
|
|
11680
|
+
# Output: Timer started for AUTH-042
|
|
11681
|
+
|
|
11682
|
+
# 3. Search for related documentation
|
|
11683
|
+
$ knowns search "password security" --type doc --plain
|
|
11684
|
+
|
|
11685
|
+
# Output:
|
|
11686
|
+
# DOC: security-patterns.md (score: 0.92)
|
|
11687
|
+
# DOC: email-templates.md (score: 0.78)
|
|
11688
|
+
|
|
11689
|
+
# 4. Read the documentation
|
|
11690
|
+
$ knowns doc view "security-patterns" --plain
|
|
11691
|
+
|
|
11692
|
+
# 5. Create implementation plan (SHARE WITH USER, WAIT FOR APPROVAL)
|
|
11693
|
+
$ knowns task edit AUTH-042 --plan $'1. Review security patterns (see [security-patterns.md](./docs/security-patterns.md))
|
|
11694
|
+
2. Design token generation with 1-hour expiry
|
|
11695
|
+
3. Create email template (see [email-templates.md](./docs/email-templates.md))
|
|
11696
|
+
4. Implement /forgot-password endpoint
|
|
11697
|
+
5. Implement /reset-password endpoint
|
|
11698
|
+
6. Add unit tests
|
|
11699
|
+
7. Update API documentation'
|
|
11700
|
+
|
|
11701
|
+
# 6. After approval, implement and check criteria as you go
|
|
11702
|
+
$ knowns task edit AUTH-042 --check-ac 1
|
|
11703
|
+
$ knowns task edit AUTH-042 --append-notes "\u2713 Implemented /forgot-password endpoint"
|
|
11704
|
+
|
|
11705
|
+
$ knowns task edit AUTH-042 --check-ac 2
|
|
11706
|
+
$ knowns task edit AUTH-042 --append-notes "\u2713 Token expiry set to 1 hour"
|
|
11707
|
+
|
|
11708
|
+
$ knowns task edit AUTH-042 --check-ac 3
|
|
11709
|
+
$ knowns task edit AUTH-042 --append-notes "\u2713 Implemented /reset-password endpoint"
|
|
11710
|
+
|
|
11711
|
+
$ knowns task edit AUTH-042 --check-ac 4
|
|
11712
|
+
$ knowns task edit AUTH-042 --append-notes "\u2713 Added 12 unit tests, 100% coverage"
|
|
11713
|
+
|
|
11714
|
+
# 7. Add final implementation notes
|
|
11715
|
+
$ knowns task edit AUTH-042 --notes $'## Summary
|
|
11716
|
+
Implemented complete password reset flow with secure token generation.
|
|
11717
|
+
|
|
11718
|
+
## Changes
|
|
11719
|
+
- Added POST /forgot-password endpoint
|
|
11720
|
+
- Added POST /reset-password endpoint
|
|
11721
|
+
- Created password_reset_tokens table
|
|
11722
|
+
- Added email template for reset link
|
|
11723
|
+
|
|
11724
|
+
## Security
|
|
11725
|
+
- Tokens use crypto.randomBytes(32)
|
|
11726
|
+
- 1-hour expiry enforced at DB level
|
|
11727
|
+
- Rate limiting: 3 requests per hour per email
|
|
11728
|
+
|
|
11729
|
+
## Tests
|
|
11730
|
+
- 12 unit tests added
|
|
11731
|
+
- Coverage: 100% for new code
|
|
11732
|
+
|
|
11733
|
+
## Documentation
|
|
11734
|
+
- Updated API.md with new endpoints'
|
|
11735
|
+
|
|
11736
|
+
# 8. Stop timer and complete
|
|
11737
|
+
$ knowns time stop
|
|
11738
|
+
$ knowns task edit AUTH-042 -s done
|
|
11739
|
+
|
|
11740
|
+
# Output: Task AUTH-042 marked as done
|
|
11741
|
+
\`\`\`
|
|
11742
|
+
|
|
11743
|
+
---
|
|
11744
|
+
|
|
11745
|
+
## Task Workflow
|
|
11480
11746
|
|
|
11481
11747
|
### Step 1: Take Task
|
|
11748
|
+
|
|
11482
11749
|
\`\`\`bash
|
|
11483
|
-
knowns task edit <id> -s in-progress -a @
|
|
11750
|
+
knowns task edit <id> -s in-progress -a @me
|
|
11484
11751
|
\`\`\`
|
|
11485
11752
|
|
|
11753
|
+
> **Note**: \`@me\` is a special keyword that assigns the task to yourself. You can also use specific usernames like \`@harry\` or \`@john\`.
|
|
11754
|
+
|
|
11486
11755
|
### Step 2: Start Time Tracking
|
|
11756
|
+
|
|
11487
11757
|
\`\`\`bash
|
|
11488
11758
|
knowns time start <id>
|
|
11489
11759
|
\`\`\`
|
|
11490
11760
|
|
|
11491
11761
|
### Step 3: Read Related Documentation
|
|
11762
|
+
|
|
11763
|
+
> **FOR AI AGENTS**: This step is MANDATORY, not optional. You must understand the codebase before planning.
|
|
11764
|
+
|
|
11492
11765
|
\`\`\`bash
|
|
11493
11766
|
# Search for related docs
|
|
11494
11767
|
knowns search "authentication" --type doc --plain
|
|
11495
11768
|
|
|
11496
11769
|
# View relevant documents
|
|
11497
11770
|
knowns doc view "API Guidelines" --plain
|
|
11771
|
+
knowns doc view "Security Patterns" --plain
|
|
11772
|
+
|
|
11773
|
+
# Also check for similar completed tasks
|
|
11774
|
+
knowns search "auth" --type task --status done --plain
|
|
11498
11775
|
\`\`\`
|
|
11499
11776
|
|
|
11500
|
-
**CRITICAL**: ALWAYS read related documentation BEFORE planning!
|
|
11777
|
+
> **CRITICAL**: ALWAYS read related documentation BEFORE planning! Understanding existing patterns and conventions prevents rework and ensures consistency.
|
|
11501
11778
|
|
|
11502
11779
|
### Step 4: Create Implementation Plan
|
|
11780
|
+
|
|
11503
11781
|
\`\`\`bash
|
|
11504
|
-
|
|
11505
|
-
|
|
11782
|
+
knowns task edit <id> --plan $'1. Research patterns (see [security-patterns.md](./security-patterns.md))
|
|
11783
|
+
2. Design middleware
|
|
11784
|
+
3. Implement
|
|
11785
|
+
4. Add tests
|
|
11786
|
+
5. Update docs'
|
|
11506
11787
|
\`\`\`
|
|
11507
11788
|
|
|
11508
|
-
**CRITICAL**:
|
|
11509
|
-
- Share plan with user and **WAIT for approval** before coding
|
|
11510
|
-
- Include doc references using \`[file.md](./path/file.md)\` format
|
|
11789
|
+
> **CRITICAL**:
|
|
11790
|
+
> - Share plan with user and **WAIT for approval** before coding
|
|
11791
|
+
> - Include doc references using \`[file.md](./path/file.md)\` format
|
|
11511
11792
|
|
|
11512
11793
|
### Step 5: Implement
|
|
11794
|
+
|
|
11513
11795
|
\`\`\`bash
|
|
11514
11796
|
# Check acceptance criteria as you complete them
|
|
11515
11797
|
knowns task edit <id> --check-ac 1 --check-ac 2 --check-ac 3
|
|
11516
11798
|
\`\`\`
|
|
11517
11799
|
|
|
11518
11800
|
### Step 6: Add Implementation Notes
|
|
11801
|
+
|
|
11519
11802
|
\`\`\`bash
|
|
11520
11803
|
# Add comprehensive notes (suitable for PR description)
|
|
11521
|
-
knowns task edit <id> --notes $'## Summary
|
|
11804
|
+
knowns task edit <id> --notes $'## Summary
|
|
11522
11805
|
|
|
11523
|
-
|
|
11806
|
+
Implemented JWT auth.
|
|
11807
|
+
|
|
11808
|
+
## Changes
|
|
11809
|
+
- Added middleware
|
|
11810
|
+
- Added tests'
|
|
11811
|
+
|
|
11812
|
+
# OR append progressively (recommended)
|
|
11524
11813
|
knowns task edit <id> --append-notes "\u2713 Implemented middleware"
|
|
11525
11814
|
knowns task edit <id> --append-notes "\u2713 Added tests"
|
|
11526
11815
|
\`\`\`
|
|
11527
11816
|
|
|
11528
11817
|
### Step 7: Stop Time Tracking
|
|
11818
|
+
|
|
11529
11819
|
\`\`\`bash
|
|
11530
11820
|
knowns time stop
|
|
11531
11821
|
\`\`\`
|
|
11532
11822
|
|
|
11533
11823
|
### Step 8: Complete Task
|
|
11824
|
+
|
|
11534
11825
|
\`\`\`bash
|
|
11535
|
-
# Mark as done (only after ALL criteria are met)
|
|
11536
11826
|
knowns task edit <id> -s done
|
|
11537
11827
|
\`\`\`
|
|
11538
11828
|
|
|
@@ -11551,11 +11841,11 @@ knowns task edit <id> -t "New title"
|
|
|
11551
11841
|
knowns task edit <id> -d "New description"
|
|
11552
11842
|
knowns task edit <id> -s in-progress
|
|
11553
11843
|
knowns task edit <id> --priority high
|
|
11554
|
-
knowns task edit <id> -a @
|
|
11844
|
+
knowns task edit <id> -a @me
|
|
11555
11845
|
|
|
11556
11846
|
# Acceptance Criteria
|
|
11557
11847
|
knowns task edit <id> --ac "New criterion" # Add
|
|
11558
|
-
knowns task edit <id> --check-ac 1 --check-ac 2 # Check
|
|
11848
|
+
knowns task edit <id> --check-ac 1 --check-ac 2 # Check (1-indexed)
|
|
11559
11849
|
knowns task edit <id> --uncheck-ac 2 # Uncheck
|
|
11560
11850
|
knowns task edit <id> --remove-ac 3 # Remove
|
|
11561
11851
|
|
|
@@ -11568,7 +11858,7 @@ knowns task edit <id> --append-notes "Progress update"
|
|
|
11568
11858
|
knowns task view <id> --plain # ALWAYS use --plain
|
|
11569
11859
|
knowns task list --plain
|
|
11570
11860
|
knowns task list --status in-progress --plain
|
|
11571
|
-
knowns task list --assignee @
|
|
11861
|
+
knowns task list --assignee @me --plain
|
|
11572
11862
|
knowns task list --tree --plain # Tree hierarchy
|
|
11573
11863
|
\`\`\`
|
|
11574
11864
|
|
|
@@ -11598,11 +11888,16 @@ knowns doc list --plain
|
|
|
11598
11888
|
knowns doc list --tag architecture --plain
|
|
11599
11889
|
knowns doc view "Doc Name" --plain
|
|
11600
11890
|
|
|
11601
|
-
# Create
|
|
11891
|
+
# Create (with optional folder)
|
|
11602
11892
|
knowns doc create "Title" -d "Description" -t "tags"
|
|
11893
|
+
knowns doc create "Title" -d "Description" -t "tags" -f "folder/path"
|
|
11603
11894
|
|
|
11604
11895
|
# Edit metadata
|
|
11605
11896
|
knowns doc edit "Doc Name" -t "New Title" --tags "new,tags"
|
|
11897
|
+
|
|
11898
|
+
# Edit content
|
|
11899
|
+
knowns doc edit "Doc Name" -c "New content" # Replace content
|
|
11900
|
+
knowns doc edit "Doc Name" -a "Appended content" # Append to content
|
|
11606
11901
|
\`\`\`
|
|
11607
11902
|
|
|
11608
11903
|
### Search
|
|
@@ -11624,51 +11919,100 @@ knowns search "bug" --status in-progress --priority high --plain
|
|
|
11624
11919
|
## Task Structure
|
|
11625
11920
|
|
|
11626
11921
|
### Title
|
|
11922
|
+
|
|
11627
11923
|
Clear summary (WHAT needs to be done).
|
|
11628
|
-
|
|
11629
|
-
|
|
11924
|
+
|
|
11925
|
+
| Bad | Good |
|
|
11926
|
+
|-----|------|
|
|
11927
|
+
| Do auth stuff | Add JWT authentication |
|
|
11928
|
+
| Fix bug | Fix login timeout on slow networks |
|
|
11929
|
+
| Update docs | Document rate limiting in API.md |
|
|
11630
11930
|
|
|
11631
11931
|
### Description
|
|
11932
|
+
|
|
11632
11933
|
Explains WHY and WHAT (not HOW). **Link related docs using \`[file.md](./path/file.md)\`**
|
|
11633
11934
|
|
|
11634
|
-
\`\`\`
|
|
11635
|
-
We need JWT authentication because sessions don't scale.
|
|
11935
|
+
\`\`\`markdown
|
|
11936
|
+
We need JWT authentication because sessions don't scale for our microservices architecture.
|
|
11636
11937
|
|
|
11637
11938
|
Related docs:
|
|
11638
|
-
- [security-patterns.md](./security-patterns.md)
|
|
11639
|
-
- [api-guidelines.md](./api-guidelines.md)
|
|
11939
|
+
- [security-patterns.md](./docs/security-patterns.md)
|
|
11940
|
+
- [api-guidelines.md](./docs/api-guidelines.md)
|
|
11640
11941
|
\`\`\`
|
|
11641
11942
|
|
|
11642
11943
|
### Acceptance Criteria
|
|
11643
|
-
**Outcome-oriented**, testable criteria. NOT implementation details.
|
|
11644
11944
|
|
|
11645
|
-
-
|
|
11646
|
-
|
|
11647
|
-
|
|
11945
|
+
**Outcome-oriented**, testable criteria. NOT implementation steps.
|
|
11946
|
+
|
|
11947
|
+
| Bad (Implementation details) | Good (Outcomes) |
|
|
11948
|
+
|------------------------------|-----------------|
|
|
11949
|
+
| Add function handleLogin() in auth.ts | User can login and receive JWT token |
|
|
11950
|
+
| Use bcrypt for hashing | Passwords are securely hashed |
|
|
11951
|
+
| Add try-catch blocks | Errors return appropriate HTTP status codes |
|
|
11648
11952
|
|
|
11649
11953
|
### Implementation Plan
|
|
11954
|
+
|
|
11650
11955
|
HOW to solve. Added AFTER taking task, BEFORE coding.
|
|
11651
11956
|
|
|
11652
|
-
\`\`\`
|
|
11653
|
-
1. Research JWT libraries (see [security-patterns.md](./security-patterns.md))
|
|
11654
|
-
2. Design token structure
|
|
11655
|
-
3. Implement middleware
|
|
11656
|
-
4. Add tests
|
|
11957
|
+
\`\`\`markdown
|
|
11958
|
+
1. Research JWT libraries (see [security-patterns.md](./docs/security-patterns.md))
|
|
11959
|
+
2. Design token structure (access + refresh tokens)
|
|
11960
|
+
3. Implement auth middleware
|
|
11961
|
+
4. Add unit tests (aim for 90%+ coverage)
|
|
11962
|
+
5. Update API.md with new endpoints
|
|
11657
11963
|
\`\`\`
|
|
11658
11964
|
|
|
11659
11965
|
### Implementation Notes
|
|
11660
|
-
Summary for PR. Added AFTER completion.
|
|
11661
11966
|
|
|
11662
|
-
|
|
11967
|
+
Summary for PR description. Added AFTER completion.
|
|
11968
|
+
|
|
11969
|
+
\`\`\`markdown
|
|
11663
11970
|
## Summary
|
|
11664
|
-
Implemented JWT auth using jsonwebtoken.
|
|
11971
|
+
Implemented JWT auth using jsonwebtoken library.
|
|
11665
11972
|
|
|
11666
11973
|
## Changes
|
|
11667
|
-
- Added middleware in src/auth.ts
|
|
11668
|
-
- Added
|
|
11974
|
+
- Added auth middleware in src/middleware/auth.ts
|
|
11975
|
+
- Added /login and /refresh endpoints
|
|
11976
|
+
- Created JWT utility functions
|
|
11977
|
+
|
|
11978
|
+
## Tests
|
|
11979
|
+
- Added 15 unit tests
|
|
11980
|
+
- Coverage: 94%
|
|
11669
11981
|
|
|
11670
11982
|
## Documentation
|
|
11671
|
-
- Updated API.md
|
|
11983
|
+
- Updated API.md with authentication section
|
|
11984
|
+
\`\`\`
|
|
11985
|
+
|
|
11986
|
+
---
|
|
11987
|
+
|
|
11988
|
+
## Error Handling
|
|
11989
|
+
|
|
11990
|
+
### Common Errors and Solutions
|
|
11991
|
+
|
|
11992
|
+
| Error | Cause | Solution |
|
|
11993
|
+
|-------|-------|----------|
|
|
11994
|
+
| \`Error: Task not found\` | Invalid task ID | Run \`knowns task list --plain\` to find correct ID |
|
|
11995
|
+
| \`Error: No active timer\` | Calling \`time stop\` without active timer | Start timer first: \`knowns time start <id>\` |
|
|
11996
|
+
| \`Error: Timer already running\` | Starting timer when one is active | Stop current: \`knowns time stop\` |
|
|
11997
|
+
| \`Error: Invalid status\` | Wrong status format | Use lowercase with hyphens: \`in-progress\`, not \`In Progress\` |
|
|
11998
|
+
| \`Error: AC index out of range\` | Checking non-existent criterion | View task first: \`knowns task view <id> --plain\` |
|
|
11999
|
+
| \`Error: Document not found\` | Wrong document name | Run \`knowns doc list --plain\` to find correct name |
|
|
12000
|
+
| \`Error: Not initialized\` | Running commands without init | Run \`knowns init\` first |
|
|
12001
|
+
|
|
12002
|
+
### Debugging Commands
|
|
12003
|
+
|
|
12004
|
+
\`\`\`bash
|
|
12005
|
+
# Check CLI version
|
|
12006
|
+
knowns --version
|
|
12007
|
+
|
|
12008
|
+
# Verify project is initialized
|
|
12009
|
+
knowns status
|
|
12010
|
+
|
|
12011
|
+
# View raw task data (for debugging)
|
|
12012
|
+
knowns task view <id> --json
|
|
12013
|
+
|
|
12014
|
+
# Check timer status
|
|
12015
|
+
knowns time status
|
|
11672
12016
|
\`\`\`
|
|
11673
12017
|
|
|
11674
12018
|
---
|
|
@@ -11678,109 +12022,193 @@ Implemented JWT auth using jsonwebtoken.
|
|
|
11678
12022
|
A task is **Done** ONLY when **ALL** criteria are met:
|
|
11679
12023
|
|
|
11680
12024
|
### Via CLI (Required)
|
|
11681
|
-
|
|
11682
|
-
|
|
11683
|
-
|
|
12025
|
+
|
|
12026
|
+
- [ ] All acceptance criteria checked: \`--check-ac <index>\`
|
|
12027
|
+
- [ ] Implementation notes added: \`--notes "..."\`
|
|
12028
|
+
- [ ] Timer stopped: \`knowns time stop\`
|
|
12029
|
+
- [ ] Status set to done: \`-s done\`
|
|
11684
12030
|
|
|
11685
12031
|
### Via Code (Required)
|
|
11686
|
-
|
|
11687
|
-
|
|
11688
|
-
|
|
11689
|
-
|
|
12032
|
+
|
|
12033
|
+
- [ ] All tests pass
|
|
12034
|
+
- [ ] Documentation updated
|
|
12035
|
+
- [ ] Code reviewed (linting, formatting)
|
|
12036
|
+
- [ ] No regressions introduced
|
|
12037
|
+
|
|
12038
|
+
---
|
|
12039
|
+
|
|
12040
|
+
## Status & Priority Reference
|
|
12041
|
+
|
|
12042
|
+
### Status Values
|
|
12043
|
+
|
|
12044
|
+
Use **lowercase with hyphens**:
|
|
12045
|
+
|
|
12046
|
+
| Status | Description | When to Use |
|
|
12047
|
+
|--------|-------------|-------------|
|
|
12048
|
+
| \`todo\` | Not started | Default for new tasks |
|
|
12049
|
+
| \`in-progress\` | Currently working | After taking task |
|
|
12050
|
+
| \`in-review\` | In code review | PR submitted |
|
|
12051
|
+
| \`blocked\` | Waiting on dependency | External blocker |
|
|
12052
|
+
| \`done\` | Completed | All criteria met |
|
|
12053
|
+
|
|
12054
|
+
### Priority Values
|
|
12055
|
+
|
|
12056
|
+
| Priority | Description |
|
|
12057
|
+
|----------|-------------|
|
|
12058
|
+
| \`low\` | Can wait, nice-to-have |
|
|
12059
|
+
| \`medium\` | Normal priority (default) |
|
|
12060
|
+
| \`high\` | Urgent, time-sensitive |
|
|
11690
12061
|
|
|
11691
12062
|
---
|
|
11692
12063
|
|
|
11693
12064
|
## Common Mistakes
|
|
11694
12065
|
|
|
11695
12066
|
| Wrong | Right |
|
|
11696
|
-
|
|
12067
|
+
|-------|-------|
|
|
11697
12068
|
| Edit .md files directly | Use \`knowns task edit\` |
|
|
11698
12069
|
| Change \`- [ ]\` to \`- [x]\` in file | Use \`--check-ac <index>\` |
|
|
11699
|
-
| Start coding without reading docs | Read docs FIRST
|
|
11700
|
-
|
|
|
11701
|
-
|
|
|
11702
|
-
|
|
|
11703
|
-
|
|
|
11704
|
-
|
|
|
11705
|
-
|
|
|
12070
|
+
| Start coding without reading docs | Read ALL related docs FIRST |
|
|
12071
|
+
| Skip \`knowns doc list\` on new project | Always list docs when starting |
|
|
12072
|
+
| Assume you know the conventions | Read CONVENTIONS/ARCHITECTURE docs |
|
|
12073
|
+
| Plan without checking docs | Read docs before planning |
|
|
12074
|
+
| Ignore similar completed tasks | Search done tasks for patterns |
|
|
12075
|
+
| Missing doc links in description/plan | Link docs using \`[file.md](./path)\` |
|
|
12076
|
+
| Forget \`--plain\` flag | Always use \`--plain\` for AI |
|
|
12077
|
+
| Code before plan approval | Share plan, WAIT for approval |
|
|
12078
|
+
| Mark done without all criteria | Check ALL criteria first |
|
|
12079
|
+
| Write implementation steps in AC | Write outcome-oriented criteria |
|
|
11706
12080
|
| Use \`"In Progress"\` or \`"Done"\` | Use \`in-progress\`, \`done\` |
|
|
12081
|
+
| Use \`@yourself\` | Use \`@me\` or specific username |
|
|
12082
|
+
| Ignore refs in task description | Follow ALL refs (\`@.knowns/...\`) before planning |
|
|
12083
|
+
| See \`@.knowns/docs/...\` but don't read | Use \`knowns doc view "<path>" --plain\` |
|
|
12084
|
+
| See \`@.knowns/tasks/task-X\` but don't check | Use \`knowns task view X --plain\` for context |
|
|
12085
|
+
| Follow only first-level refs | Recursively follow nested refs until complete |
|
|
11707
12086
|
|
|
11708
12087
|
---
|
|
11709
12088
|
|
|
11710
|
-
##
|
|
11711
|
-
|
|
11712
|
-
### Critical Rules
|
|
11713
|
-
- **ALWAYS use \`--plain\` flag** for AI-readable output
|
|
11714
|
-
- **Read documentation BEFORE planning**: \`knowns search "keyword" --type doc --plain\`
|
|
11715
|
-
- **Link docs in tasks**: Use \`[file.md](./path/file.md)\` or \`[mapper.md](./patterns/mapper.md)\`
|
|
11716
|
-
- **Share plan before coding**: Get approval first
|
|
11717
|
-
- **Start time tracking**: \`knowns time start <id>\` before work
|
|
11718
|
-
- **Stop time tracking**: \`knowns time stop\` after work
|
|
11719
|
-
- **Check criteria as you go**: Don't wait until end
|
|
11720
|
-
- **Add notes progressively**: Use \`--append-notes\`
|
|
12089
|
+
## Platform-Specific Notes
|
|
11721
12090
|
|
|
11722
12091
|
### Multi-line Input
|
|
11723
|
-
- **Bash/Zsh**: \`$'Line1\\nLine2\\nLine3'\`
|
|
11724
|
-
- **PowerShell**: \`"Line1\\\`nLine2\\\`nLine3"\`
|
|
11725
|
-
- **Portable**: \`"$(printf 'Line1\\nLine2\\nLine3')"\`
|
|
11726
12092
|
|
|
11727
|
-
|
|
11728
|
-
|
|
11729
|
-
|
|
11730
|
-
|
|
11731
|
-
|
|
11732
|
-
|
|
12093
|
+
Different shells handle multi-line strings differently:
|
|
12094
|
+
|
|
12095
|
+
**Bash / Zsh (Recommended)**
|
|
12096
|
+
\`\`\`bash
|
|
12097
|
+
knowns task edit <id> --plan $'1. First step\\n2. Second step\\n3. Third step'
|
|
12098
|
+
\`\`\`
|
|
12099
|
+
|
|
12100
|
+
**PowerShell**
|
|
12101
|
+
\`\`\`powershell
|
|
12102
|
+
knowns task edit <id> --plan "1. First step\`n2. Second step\`n3. Third step"
|
|
12103
|
+
\`\`\`
|
|
12104
|
+
|
|
12105
|
+
**Cross-platform (Using printf)**
|
|
12106
|
+
\`\`\`bash
|
|
12107
|
+
knowns task edit <id> --plan "$(printf '1. First step\\n2. Second step\\n3. Third step')"
|
|
12108
|
+
\`\`\`
|
|
12109
|
+
|
|
12110
|
+
**Using heredoc (for long content)**
|
|
12111
|
+
\`\`\`bash
|
|
12112
|
+
knowns task edit <id> --plan "$(cat <<EOF
|
|
12113
|
+
1. First step
|
|
12114
|
+
2. Second step
|
|
12115
|
+
3. Third step
|
|
12116
|
+
EOF
|
|
12117
|
+
)"
|
|
12118
|
+
\`\`\`
|
|
12119
|
+
|
|
12120
|
+
### Path Separators
|
|
12121
|
+
|
|
12122
|
+
- **Unix/macOS**: Use forward slashes: \`./docs/api.md\`
|
|
12123
|
+
- **Windows**: Both work, but prefer forward slashes for consistency
|
|
11733
12124
|
|
|
11734
12125
|
---
|
|
11735
12126
|
|
|
11736
|
-
##
|
|
12127
|
+
## Best Practices Checklist
|
|
12128
|
+
|
|
12129
|
+
### For AI Agents: Session Start
|
|
11737
12130
|
|
|
11738
|
-
|
|
11739
|
-
-
|
|
11740
|
-
-
|
|
11741
|
-
-
|
|
11742
|
-
- \`done\` - Completed
|
|
11743
|
-
- \`blocked\` - Waiting on dependency
|
|
12131
|
+
- [ ] List all docs: \`knowns doc list --plain\`
|
|
12132
|
+
- [ ] Read README/ARCHITECTURE docs
|
|
12133
|
+
- [ ] Understand coding conventions
|
|
12134
|
+
- [ ] Review current task backlog
|
|
11744
12135
|
|
|
11745
|
-
|
|
11746
|
-
|
|
11747
|
-
-
|
|
11748
|
-
-
|
|
12136
|
+
### Before Starting Work
|
|
12137
|
+
|
|
12138
|
+
- [ ] Task has clear acceptance criteria
|
|
12139
|
+
- [ ] ALL refs in task followed (\`@.knowns/...\`)
|
|
12140
|
+
- [ ] Nested refs recursively followed until complete context gathered
|
|
12141
|
+
- [ ] Related docs searched: \`knowns search "keyword" --type doc --plain\`
|
|
12142
|
+
- [ ] ALL relevant docs read: \`knowns doc view "Doc Name" --plain\`
|
|
12143
|
+
- [ ] Similar done tasks reviewed for patterns
|
|
12144
|
+
- [ ] Task assigned to self: \`-a @me\`
|
|
12145
|
+
- [ ] Status set to in-progress: \`-s in-progress\`
|
|
12146
|
+
- [ ] Timer started: \`knowns time start <id>\`
|
|
12147
|
+
|
|
12148
|
+
### During Work
|
|
12149
|
+
|
|
12150
|
+
- [ ] Implementation plan created and approved
|
|
12151
|
+
- [ ] Doc links included in plan: \`[file.md](./path/file.md)\`
|
|
12152
|
+
- [ ] Criteria checked as completed: \`--check-ac <index>\`
|
|
12153
|
+
- [ ] Progress notes appended: \`--append-notes "\u2713 ..."\`
|
|
12154
|
+
|
|
12155
|
+
### After Work
|
|
12156
|
+
|
|
12157
|
+
- [ ] All acceptance criteria checked
|
|
12158
|
+
- [ ] Implementation notes added: \`--notes "..."\`
|
|
12159
|
+
- [ ] Timer stopped: \`knowns time stop\`
|
|
12160
|
+
- [ ] Tests passing
|
|
12161
|
+
- [ ] Documentation updated
|
|
12162
|
+
- [ ] Status set to done: \`-s done\`
|
|
11749
12163
|
|
|
11750
12164
|
---
|
|
11751
12165
|
|
|
11752
|
-
## Quick Reference
|
|
12166
|
+
## Quick Reference Card
|
|
11753
12167
|
|
|
11754
12168
|
\`\`\`bash
|
|
11755
|
-
#
|
|
12169
|
+
# === AGENT INITIALIZATION (Once per session) ===
|
|
12170
|
+
knowns doc list --plain
|
|
12171
|
+
knowns doc view "README" --plain
|
|
12172
|
+
knowns doc view "ARCHITECTURE" --plain
|
|
12173
|
+
knowns doc view "CONVENTIONS" --plain
|
|
12174
|
+
|
|
12175
|
+
# === FULL WORKFLOW ===
|
|
11756
12176
|
knowns task create "Title" -d "Description" --ac "Criterion"
|
|
11757
|
-
knowns task edit <id> -s in-progress -a @
|
|
12177
|
+
knowns task edit <id> -s in-progress -a @me
|
|
11758
12178
|
knowns time start <id>
|
|
11759
12179
|
knowns search "keyword" --type doc --plain
|
|
11760
12180
|
knowns doc view "Doc Name" --plain
|
|
12181
|
+
knowns search "keyword" --type task --status done --plain # Learn from history
|
|
11761
12182
|
knowns task edit <id> --plan $'1. Step (see [file.md](./file.md))\\n2. Step'
|
|
11762
|
-
# ... implement
|
|
12183
|
+
# ... wait for approval, then implement ...
|
|
11763
12184
|
knowns task edit <id> --check-ac 1 --check-ac 2
|
|
11764
|
-
knowns task edit <id> --append-notes "\u2713 Completed"
|
|
12185
|
+
knowns task edit <id> --append-notes "\u2713 Completed feature"
|
|
11765
12186
|
knowns time stop
|
|
11766
12187
|
knowns task edit <id> -s done
|
|
11767
12188
|
|
|
11768
|
-
#
|
|
12189
|
+
# === VIEW & SEARCH ===
|
|
11769
12190
|
knowns task view <id> --plain
|
|
11770
|
-
|
|
11771
|
-
# List tasks
|
|
11772
12191
|
knowns task list --plain
|
|
11773
|
-
knowns task list --status in-progress --assignee @
|
|
11774
|
-
|
|
11775
|
-
# Search
|
|
12192
|
+
knowns task list --status in-progress --assignee @me --plain
|
|
11776
12193
|
knowns search "query" --plain
|
|
11777
12194
|
knowns search "bug" --type task --status in-progress --plain
|
|
12195
|
+
|
|
12196
|
+
# === TIME TRACKING ===
|
|
12197
|
+
knowns time start <id>
|
|
12198
|
+
knowns time stop
|
|
12199
|
+
knowns time status
|
|
12200
|
+
knowns time report --from "2025-12-01" --to "2025-12-31"
|
|
12201
|
+
|
|
12202
|
+
# === DOCUMENTATION ===
|
|
12203
|
+
knowns doc list --plain
|
|
12204
|
+
knowns doc view "path/doc-name" --plain
|
|
12205
|
+
knowns doc create "Title" -d "Description" -t "tags" -f "folder"
|
|
12206
|
+
knowns doc edit "doc-name" -c "New content"
|
|
12207
|
+
knowns doc edit "doc-name" -a "Appended content"
|
|
11778
12208
|
\`\`\`
|
|
11779
12209
|
|
|
11780
12210
|
---
|
|
11781
12211
|
|
|
11782
|
-
**Last Updated**: 2025-12-26
|
|
11783
|
-
**Version**: 2.1.0
|
|
11784
12212
|
**Maintained By**: Knowns CLI Team
|
|
11785
12213
|
|
|
11786
12214
|
<!-- KNOWNS GUIDELINES END -->
|
|
@@ -12029,7 +12457,7 @@ var initCommand = new Command("init").description("Initialize .knowns/ folder in
|
|
|
12029
12457
|
});
|
|
12030
12458
|
// src/commands/task.ts
|
|
12031
12459
|
import { mkdir as mkdir4, readdir as readdir2, unlink } from "fs/promises";
|
|
12032
|
-
import { join as
|
|
12460
|
+
import { join as join8 } from "path";
|
|
12033
12461
|
|
|
12034
12462
|
// src/utils/doc-links.ts
|
|
12035
12463
|
import { existsSync as existsSync3 } from "fs";
|
|
@@ -12114,6 +12542,86 @@ function findProjectRoot(startPath = process.cwd()) {
|
|
|
12114
12542
|
return null;
|
|
12115
12543
|
}
|
|
12116
12544
|
|
|
12545
|
+
// src/utils/mention-refs.ts
|
|
12546
|
+
var TASK_MENTION_REGEX = /@task-(\d+)/g;
|
|
12547
|
+
var DOC_MENTION_REGEX = /@docs?\/([^\s|)]+)/g;
|
|
12548
|
+
function sanitizeTitle(title) {
|
|
12549
|
+
return title.replace(/[<>:"/\\|?*]/g, "").replace(/\s+/g, " ").trim().slice(0, 50);
|
|
12550
|
+
}
|
|
12551
|
+
function transformTaskMention(taskId, tasks) {
|
|
12552
|
+
const task = tasks.get(taskId);
|
|
12553
|
+
if (task) {
|
|
12554
|
+
const sanitizedTitle = sanitizeTitle(task.title);
|
|
12555
|
+
return `@.knowns/tasks/task-${taskId} - ${sanitizedTitle}.md`;
|
|
12556
|
+
}
|
|
12557
|
+
return `@.knowns/tasks/task-${taskId}.md`;
|
|
12558
|
+
}
|
|
12559
|
+
function transformDocMention(docPath) {
|
|
12560
|
+
const normalizedPath = docPath.endsWith(".md") ? docPath : `${docPath}.md`;
|
|
12561
|
+
return `@.knowns/docs/${normalizedPath}`;
|
|
12562
|
+
}
|
|
12563
|
+
function transformMentionsToRefs(text, tasks) {
|
|
12564
|
+
let result = text;
|
|
12565
|
+
result = result.replace(TASK_MENTION_REGEX, (match, taskId) => {
|
|
12566
|
+
return transformTaskMention(taskId, tasks);
|
|
12567
|
+
});
|
|
12568
|
+
result = result.replace(DOC_MENTION_REGEX, (match, docPath) => {
|
|
12569
|
+
return transformDocMention(docPath);
|
|
12570
|
+
});
|
|
12571
|
+
return result;
|
|
12572
|
+
}
|
|
12573
|
+
function buildTaskMap(tasks) {
|
|
12574
|
+
const map = new Map;
|
|
12575
|
+
for (const task of tasks) {
|
|
12576
|
+
map.set(task.id, task);
|
|
12577
|
+
}
|
|
12578
|
+
return map;
|
|
12579
|
+
}
|
|
12580
|
+
|
|
12581
|
+
// src/utils/notify-server.ts
|
|
12582
|
+
import { existsSync as existsSync5, readFileSync } from "fs";
|
|
12583
|
+
import { join as join7 } from "path";
|
|
12584
|
+
var DEFAULT_PORT = 6420;
|
|
12585
|
+
function getServerPort() {
|
|
12586
|
+
try {
|
|
12587
|
+
const projectRoot = findProjectRoot();
|
|
12588
|
+
if (!projectRoot)
|
|
12589
|
+
return DEFAULT_PORT;
|
|
12590
|
+
const configPath = join7(projectRoot, ".knowns/config.json");
|
|
12591
|
+
if (!existsSync5(configPath))
|
|
12592
|
+
return DEFAULT_PORT;
|
|
12593
|
+
const content = readFileSync(configPath, "utf-8");
|
|
12594
|
+
const config = JSON.parse(content);
|
|
12595
|
+
const port = config.settings?.serverPort;
|
|
12596
|
+
return typeof port === "number" ? port : DEFAULT_PORT;
|
|
12597
|
+
} catch {
|
|
12598
|
+
return DEFAULT_PORT;
|
|
12599
|
+
}
|
|
12600
|
+
}
|
|
12601
|
+
async function notifyTaskUpdate(taskId) {
|
|
12602
|
+
try {
|
|
12603
|
+
const port = getServerPort();
|
|
12604
|
+
const response = await fetch(`http://localhost:${port}/api/notify`, {
|
|
12605
|
+
method: "POST",
|
|
12606
|
+
headers: { "Content-Type": "application/json" },
|
|
12607
|
+
body: JSON.stringify({ taskId }),
|
|
12608
|
+
signal: AbortSignal.timeout(1000)
|
|
12609
|
+
});
|
|
12610
|
+
if (response.ok) {}
|
|
12611
|
+
} catch {}
|
|
12612
|
+
}
|
|
12613
|
+
async function notifyDocUpdate(docPath) {
|
|
12614
|
+
try {
|
|
12615
|
+
const port = getServerPort();
|
|
12616
|
+
await fetch(`http://localhost:${port}/api/notify`, {
|
|
12617
|
+
method: "POST",
|
|
12618
|
+
headers: { "Content-Type": "application/json" },
|
|
12619
|
+
body: JSON.stringify({ type: "docs:updated", docPath }),
|
|
12620
|
+
signal: AbortSignal.timeout(1000)
|
|
12621
|
+
});
|
|
12622
|
+
} catch {}
|
|
12623
|
+
}
|
|
12624
|
+
|
|
12117
12625
|
// src/commands/task.ts
|
|
12118
12626
|
function getFileStore() {
|
|
12119
12627
|
const projectRoot = findProjectRoot();
|
|
@@ -12153,6 +12661,9 @@ async function formatTask(task, fileStore, plain = false) {
|
|
|
12153
12661
|
const border = "-".repeat(50);
|
|
12154
12662
|
const titleBorder = "=".repeat(50);
|
|
12155
12663
|
const output2 = [];
|
|
12664
|
+
const allTasks = await fileStore.getAllTasks();
|
|
12665
|
+
const taskMap = buildTaskMap(allTasks);
|
|
12666
|
+
const transformText = (text) => transformMentionsToRefs(text, taskMap);
|
|
12156
12667
|
const projectRoot2 = findProjectRoot();
|
|
12157
12668
|
if (projectRoot2) {
|
|
12158
12669
|
const filename = `task-${task.id} - ${task.title.replace(/[^a-z0-9]+/gi, "-")}`;
|
|
@@ -12204,7 +12715,7 @@ async function formatTask(task, fileStore, plain = false) {
|
|
|
12204
12715
|
if (task.description) {
|
|
12205
12716
|
output2.push("Description:");
|
|
12206
12717
|
output2.push(border);
|
|
12207
|
-
output2.push(task.description);
|
|
12718
|
+
output2.push(transformText(task.description));
|
|
12208
12719
|
output2.push("");
|
|
12209
12720
|
}
|
|
12210
12721
|
if (task.acceptanceCriteria.length > 0) {
|
|
@@ -12212,20 +12723,20 @@ async function formatTask(task, fileStore, plain = false) {
|
|
|
12212
12723
|
output2.push(border);
|
|
12213
12724
|
for (const [i, ac] of task.acceptanceCriteria.entries()) {
|
|
12214
12725
|
const checkbox = ac.completed ? "[x]" : "[ ]";
|
|
12215
|
-
output2.push(`- ${checkbox} #${i + 1} ${ac.text}`);
|
|
12726
|
+
output2.push(`- ${checkbox} #${i + 1} ${transformText(ac.text)}`);
|
|
12216
12727
|
}
|
|
12217
12728
|
output2.push("");
|
|
12218
12729
|
}
|
|
12219
12730
|
if (task.implementationPlan) {
|
|
12220
12731
|
output2.push("Implementation Plan:");
|
|
12221
12732
|
output2.push(border);
|
|
12222
|
-
output2.push(task.implementationPlan);
|
|
12733
|
+
output2.push(transformText(task.implementationPlan));
|
|
12223
12734
|
output2.push("");
|
|
12224
12735
|
}
|
|
12225
12736
|
if (task.implementationNotes) {
|
|
12226
12737
|
output2.push("Implementation Notes:");
|
|
12227
12738
|
output2.push(border);
|
|
12228
|
-
output2.push(task.implementationNotes);
|
|
12739
|
+
output2.push(transformText(task.implementationNotes));
|
|
12229
12740
|
output2.push("");
|
|
12230
12741
|
}
|
|
12231
12742
|
if (projectRoot2) {
|
|
@@ -12504,6 +13015,7 @@ var createCommand2 = new Command("create").description("Create a new task").argu
|
|
|
12504
13015
|
timeSpent: 0,
|
|
12505
13016
|
timeEntries: []
|
|
12506
13017
|
});
|
|
13018
|
+
await notifyTaskUpdate(task.id);
|
|
12507
13019
|
console.log(source_default.green(`\u2713 Created task-${task.id}: ${task.title}`));
|
|
12508
13020
|
if (options2.parent) {
|
|
12509
13021
|
console.log(source_default.gray(` Subtask of: ${options2.parent}`));
|
|
@@ -12668,6 +13180,7 @@ var editCommand = new Command("edit").description("Edit task properties").argume
|
|
|
12668
13180
|
updates.implementationNotes = existingNotes + separator + options2.appendNotes;
|
|
12669
13181
|
}
|
|
12670
13182
|
await fileStore.updateTask(id, updates);
|
|
13183
|
+
await notifyTaskUpdate(id);
|
|
12671
13184
|
console.log(source_default.green(`\u2713 Updated task-${id}`));
|
|
12672
13185
|
const changes = [];
|
|
12673
13186
|
if (options2.title)
|
|
@@ -12724,17 +13237,17 @@ var archiveCommand = new Command("archive").description("Archive a task").argume
|
|
|
12724
13237
|
console.error(source_default.red(`\u2717 Task ${id} not found`));
|
|
12725
13238
|
process.exit(1);
|
|
12726
13239
|
}
|
|
12727
|
-
const archiveDir =
|
|
13240
|
+
const archiveDir = join8(projectRoot, ".knowns", "archive");
|
|
12728
13241
|
await mkdir4(archiveDir, { recursive: true });
|
|
12729
|
-
const tasksPath =
|
|
13242
|
+
const tasksPath = join8(projectRoot, ".knowns", "tasks");
|
|
12730
13243
|
const files = await readdir2(tasksPath);
|
|
12731
13244
|
const taskFile = files.find((f) => f.startsWith(`task-${id} -`));
|
|
12732
13245
|
if (!taskFile) {
|
|
12733
13246
|
console.error(source_default.red(`\u2717 Task file for ${id} not found`));
|
|
12734
13247
|
process.exit(1);
|
|
12735
13248
|
}
|
|
12736
|
-
const oldPath =
|
|
12737
|
-
const newPath =
|
|
13249
|
+
const oldPath = join8(tasksPath, taskFile);
|
|
13250
|
+
const newPath = join8(archiveDir, taskFile);
|
|
12738
13251
|
const content = await Bun.file(oldPath).text();
|
|
12739
13252
|
await Bun.write(newPath, content);
|
|
12740
13253
|
await unlink(oldPath);
|
|
@@ -12755,16 +13268,16 @@ var unarchiveCommand = new Command("unarchive").description("Restore archived ta
|
|
|
12755
13268
|
console.error(source_default.gray(' Run "knowns init" to initialize'));
|
|
12756
13269
|
process.exit(1);
|
|
12757
13270
|
}
|
|
12758
|
-
const archiveDir =
|
|
12759
|
-
const tasksPath =
|
|
13271
|
+
const archiveDir = join8(projectRoot, ".knowns", "archive");
|
|
13272
|
+
const tasksPath = join8(projectRoot, ".knowns", "tasks");
|
|
12760
13273
|
const files = await readdir2(archiveDir);
|
|
12761
13274
|
const taskFile = files.find((f) => f.startsWith(`task-${id} -`));
|
|
12762
13275
|
if (!taskFile) {
|
|
12763
13276
|
console.error(source_default.red(`\u2717 Archived task ${id} not found`));
|
|
12764
13277
|
process.exit(1);
|
|
12765
13278
|
}
|
|
12766
|
-
const archivePath =
|
|
12767
|
-
const tasksFilePath =
|
|
13279
|
+
const archivePath = join8(archiveDir, taskFile);
|
|
13280
|
+
const tasksFilePath = join8(tasksPath, taskFile);
|
|
12768
13281
|
const content = await Bun.file(archivePath).text();
|
|
12769
13282
|
await Bun.write(tasksFilePath, content);
|
|
12770
13283
|
await unlink(archivePath);
|
|
@@ -13355,9 +13868,9 @@ var boardCommand = new Command("board").description("Display Kanban board").opti
|
|
|
13355
13868
|
}
|
|
13356
13869
|
});
|
|
13357
13870
|
// src/commands/search.ts
|
|
13358
|
-
import { existsSync as
|
|
13871
|
+
import { existsSync as existsSync6 } from "fs";
|
|
13359
13872
|
import { readFile as readFile2, readdir as readdir3 } from "fs/promises";
|
|
13360
|
-
import { join as
|
|
13873
|
+
import { join as join9 } from "path";
|
|
13361
13874
|
var import_gray_matter2 = __toESM(require_gray_matter(), 1);
|
|
13362
13875
|
function getFileStore3() {
|
|
13363
13876
|
const projectRoot = findProjectRoot();
|
|
@@ -13409,8 +13922,8 @@ function calculateDocScore(doc, query) {
|
|
|
13409
13922
|
return score;
|
|
13410
13923
|
}
|
|
13411
13924
|
async function searchDocs(query, projectRoot) {
|
|
13412
|
-
const docsDir =
|
|
13413
|
-
if (!
|
|
13925
|
+
const docsDir = join9(projectRoot, ".knowns", "docs");
|
|
13926
|
+
if (!existsSync6(docsDir)) {
|
|
13414
13927
|
return [];
|
|
13415
13928
|
}
|
|
13416
13929
|
try {
|
|
@@ -13418,7 +13931,7 @@ async function searchDocs(query, projectRoot) {
|
|
|
13418
13931
|
const mdFiles = files.filter((f) => f.endsWith(".md"));
|
|
13419
13932
|
const results = [];
|
|
13420
13933
|
for (const file of mdFiles) {
|
|
13421
|
-
const content = await readFile2(
|
|
13934
|
+
const content = await readFile2(join9(docsDir, file), "utf-8");
|
|
13422
13935
|
const { data, content: docContent } = import_gray_matter2.default(content);
|
|
13423
13936
|
const metadata = data;
|
|
13424
13937
|
const doc = {
|
|
@@ -13676,6 +14189,7 @@ var stopCommand = new Command("stop").description("Stop current timer and save t
|
|
|
13676
14189
|
timeEntries: task.timeEntries,
|
|
13677
14190
|
timeSpent: task.timeSpent
|
|
13678
14191
|
});
|
|
14192
|
+
await notifyTaskUpdate(taskId);
|
|
13679
14193
|
}
|
|
13680
14194
|
data.active = null;
|
|
13681
14195
|
await saveTimeData(projectRoot, data);
|
|
@@ -13847,6 +14361,7 @@ var addCommand = new Command("add").description("Manually add time entry to a ta
|
|
|
13847
14361
|
timeEntries: task.timeEntries,
|
|
13848
14362
|
timeSpent: task.timeSpent
|
|
13849
14363
|
});
|
|
14364
|
+
await notifyTaskUpdate(taskId);
|
|
13850
14365
|
console.log(source_default.green(`\u2713 Added ${formatDuration(seconds)} to #${taskId}: ${task.title}`));
|
|
13851
14366
|
if (options2.note) {
|
|
13852
14367
|
console.log(source_default.gray(` Note: ${options2.note}`));
|
|
@@ -13999,13 +14514,16 @@ timeCommand.addCommand(statusCommand);
|
|
|
13999
14514
|
timeCommand.addCommand(logCommand);
|
|
14000
14515
|
timeCommand.addCommand(addCommand);
|
|
14001
14516
|
timeCommand.addCommand(reportCommand);
|
|
14517
|
+
// src/commands/browser.ts
|
|
14518
|
+
import { existsSync as existsSync8 } from "fs";
|
|
14519
|
+
import { mkdir as mkdir6, readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
|
|
14520
|
+
import { join as join11 } from "path";
|
|
14521
|
+
|
|
14002
14522
|
// src/server/index.ts
|
|
14003
|
-
import {
|
|
14004
|
-
import { existsSync as existsSync6 } from "fs";
|
|
14523
|
+
import { existsSync as existsSync7 } from "fs";
|
|
14005
14524
|
import { mkdir as mkdir5, readFile as readFile3, readdir as readdir4, writeFile as writeFile2 } from "fs/promises";
|
|
14006
|
-
import { join as
|
|
14525
|
+
import { join as join10, relative } from "path";
|
|
14007
14526
|
var import_gray_matter3 = __toESM(require_gray_matter(), 1);
|
|
14008
|
-
var buildVersion = Date.now();
|
|
14009
14527
|
async function startServer(options2) {
|
|
14010
14528
|
const { port, projectRoot, open } = options2;
|
|
14011
14529
|
const store = new FileStore(projectRoot);
|
|
@@ -14019,89 +14537,13 @@ async function startServer(options2) {
|
|
|
14019
14537
|
const currentDir = import.meta.dir;
|
|
14020
14538
|
let packageRoot = currentDir;
|
|
14021
14539
|
if (currentDir.endsWith("/dist")) {
|
|
14022
|
-
packageRoot =
|
|
14540
|
+
packageRoot = join10(currentDir, "..");
|
|
14023
14541
|
} else if (currentDir.includes("/src/server")) {
|
|
14024
|
-
packageRoot =
|
|
14025
|
-
}
|
|
14026
|
-
const uiSourcePath = join9(packageRoot, "src", "ui");
|
|
14027
|
-
const uiDistPath = join9(packageRoot, "dist", "ui");
|
|
14028
|
-
let shouldBuild = false;
|
|
14029
|
-
if (existsSync6(join9(uiDistPath, "main.js"))) {
|
|
14030
|
-
shouldBuild = false;
|
|
14031
|
-
} else if (existsSync6(join9(uiSourcePath, "index.html"))) {
|
|
14032
|
-
shouldBuild = true;
|
|
14033
|
-
} else {
|
|
14034
|
-
throw new Error(`UI files not found. Tried:
|
|
14035
|
-
- ${uiDistPath} (${existsSync6(uiDistPath) ? "exists but no main.js" : "not found"})
|
|
14036
|
-
- ${uiSourcePath} (${existsSync6(uiSourcePath) ? "exists but no index.html" : "not found"})
|
|
14037
|
-
Package root: ${packageRoot}
|
|
14038
|
-
Current dir: ${currentDir}`);
|
|
14039
|
-
}
|
|
14040
|
-
const buildUI = async () => {
|
|
14041
|
-
if (!shouldBuild) {
|
|
14042
|
-
return true;
|
|
14043
|
-
}
|
|
14044
|
-
console.log("Building UI...");
|
|
14045
|
-
const startTime = Date.now();
|
|
14046
|
-
if (!existsSync6(uiDistPath)) {
|
|
14047
|
-
await mkdir5(uiDistPath, { recursive: true });
|
|
14048
|
-
}
|
|
14049
|
-
const buildResult = await Bun.build({
|
|
14050
|
-
entrypoints: [join9(uiSourcePath, "main.tsx")],
|
|
14051
|
-
outdir: uiDistPath,
|
|
14052
|
-
target: "browser",
|
|
14053
|
-
minify: false,
|
|
14054
|
-
sourcemap: "inline",
|
|
14055
|
-
define: {
|
|
14056
|
-
"process.env.NODE_ENV": JSON.stringify("development")
|
|
14057
|
-
}
|
|
14058
|
-
});
|
|
14059
|
-
if (!buildResult.success) {
|
|
14060
|
-
console.error("Build errors:", buildResult.logs);
|
|
14061
|
-
return false;
|
|
14062
|
-
}
|
|
14063
|
-
const cssResult = await Bun.build({
|
|
14064
|
-
entrypoints: [join9(uiSourcePath, "index.css")],
|
|
14065
|
-
outdir: uiDistPath,
|
|
14066
|
-
target: "browser",
|
|
14067
|
-
minify: false
|
|
14068
|
-
});
|
|
14069
|
-
if (!cssResult.success) {
|
|
14070
|
-
console.error("CSS build errors:", cssResult.logs);
|
|
14071
|
-
}
|
|
14072
|
-
buildVersion = Date.now();
|
|
14073
|
-
console.log(`UI built in ${Date.now() - startTime}ms`);
|
|
14074
|
-
return true;
|
|
14075
|
-
};
|
|
14076
|
-
if (shouldBuild) {
|
|
14077
|
-
if (!await buildUI()) {
|
|
14078
|
-
throw new Error("Failed to build UI");
|
|
14079
|
-
}
|
|
14542
|
+
packageRoot = join10(currentDir, "..", "..");
|
|
14080
14543
|
}
|
|
14081
|
-
|
|
14082
|
-
if (
|
|
14083
|
-
|
|
14084
|
-
watcher = watch(uiSourcePath, { recursive: true }, async (_event, filename) => {
|
|
14085
|
-
if (!filename)
|
|
14086
|
-
return;
|
|
14087
|
-
if (!filename.endsWith(".tsx") && !filename.endsWith(".ts") && !filename.endsWith(".css")) {
|
|
14088
|
-
return;
|
|
14089
|
-
}
|
|
14090
|
-
if (rebuildTimeout) {
|
|
14091
|
-
clearTimeout(rebuildTimeout);
|
|
14092
|
-
}
|
|
14093
|
-
rebuildTimeout = setTimeout(async () => {
|
|
14094
|
-
console.log(`File changed: ${filename}`);
|
|
14095
|
-
const success = await buildUI();
|
|
14096
|
-
if (success) {
|
|
14097
|
-
broadcast({ type: "reload" });
|
|
14098
|
-
}
|
|
14099
|
-
}, 100);
|
|
14100
|
-
});
|
|
14101
|
-
process.on("SIGINT", () => {
|
|
14102
|
-
watcher?.close();
|
|
14103
|
-
process.exit(0);
|
|
14104
|
-
});
|
|
14544
|
+
const uiDistPath = join10(packageRoot, "dist", "ui");
|
|
14545
|
+
if (!existsSync7(join10(uiDistPath, "index.html"))) {
|
|
14546
|
+
throw new Error(`UI build not found at ${uiDistPath}. Run 'bun run build:ui' first.`);
|
|
14105
14547
|
}
|
|
14106
14548
|
const server = Bun.serve({
|
|
14107
14549
|
port,
|
|
@@ -14119,89 +14561,31 @@ Current dir: ${currentDir}`);
|
|
|
14119
14561
|
if (url.pathname.startsWith("/api/")) {
|
|
14120
14562
|
return handleAPI(req, url, store, broadcast);
|
|
14121
14563
|
}
|
|
14122
|
-
if (url.pathname
|
|
14123
|
-
const
|
|
14564
|
+
if (url.pathname.startsWith("/assets/")) {
|
|
14565
|
+
const filePath = join10(uiDistPath, url.pathname);
|
|
14566
|
+
const file = Bun.file(filePath);
|
|
14124
14567
|
if (await file.exists()) {
|
|
14568
|
+
const ext = url.pathname.split(".").pop();
|
|
14569
|
+
const contentType = ext === "js" ? "application/javascript" : ext === "css" ? "text/css" : ext === "svg" ? "image/svg+xml" : ext === "png" ? "image/png" : ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "woff2" ? "font/woff2" : ext === "woff" ? "font/woff" : "application/octet-stream";
|
|
14125
14570
|
return new Response(file, {
|
|
14126
14571
|
headers: {
|
|
14127
|
-
"Content-Type":
|
|
14128
|
-
"Cache-Control": "
|
|
14572
|
+
"Content-Type": contentType,
|
|
14573
|
+
"Cache-Control": "public, max-age=31536000, immutable"
|
|
14129
14574
|
}
|
|
14130
14575
|
});
|
|
14131
14576
|
}
|
|
14132
14577
|
}
|
|
14133
|
-
if (url.pathname === "/index.
|
|
14134
|
-
const
|
|
14135
|
-
if (await
|
|
14136
|
-
return new Response(
|
|
14578
|
+
if (url.pathname === "/" || url.pathname === "/index.html" || !url.pathname.includes(".")) {
|
|
14579
|
+
const indexFile = Bun.file(join10(uiDistPath, "index.html"));
|
|
14580
|
+
if (await indexFile.exists()) {
|
|
14581
|
+
return new Response(indexFile, {
|
|
14137
14582
|
headers: {
|
|
14138
|
-
"Content-Type": "text/
|
|
14583
|
+
"Content-Type": "text/html",
|
|
14139
14584
|
"Cache-Control": "no-cache, no-store, must-revalidate"
|
|
14140
14585
|
}
|
|
14141
14586
|
});
|
|
14142
14587
|
}
|
|
14143
14588
|
}
|
|
14144
|
-
if (url.pathname === "/" || url.pathname === "/index.html") {
|
|
14145
|
-
const html = `<!DOCTYPE html>
|
|
14146
|
-
<html lang="en">
|
|
14147
|
-
<head>
|
|
14148
|
-
<meta charset="UTF-8" />
|
|
14149
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
14150
|
-
<title>Knowns - Task Management</title>
|
|
14151
|
-
<link rel="stylesheet" href="/index.css?v=${buildVersion}" />
|
|
14152
|
-
</head>
|
|
14153
|
-
<body>
|
|
14154
|
-
<div id="root"></div>
|
|
14155
|
-
<script type="module" src="/main.js?v=${buildVersion}"></script>
|
|
14156
|
-
<script>
|
|
14157
|
-
// Live reload via WebSocket
|
|
14158
|
-
(function() {
|
|
14159
|
-
let ws;
|
|
14160
|
-
let reconnectInterval;
|
|
14161
|
-
|
|
14162
|
-
function connect() {
|
|
14163
|
-
ws = new WebSocket('ws://' + location.host + '/ws');
|
|
14164
|
-
|
|
14165
|
-
ws.onopen = function() {
|
|
14166
|
-
console.log('[LiveReload] Connected');
|
|
14167
|
-
if (reconnectInterval) {
|
|
14168
|
-
clearInterval(reconnectInterval);
|
|
14169
|
-
reconnectInterval = null;
|
|
14170
|
-
}
|
|
14171
|
-
};
|
|
14172
|
-
|
|
14173
|
-
ws.onmessage = function(event) {
|
|
14174
|
-
const data = JSON.parse(event.data);
|
|
14175
|
-
if (data.type === 'reload') {
|
|
14176
|
-
console.log('[LiveReload] Reloading...');
|
|
14177
|
-
location.reload();
|
|
14178
|
-
}
|
|
14179
|
-
};
|
|
14180
|
-
|
|
14181
|
-
ws.onclose = function() {
|
|
14182
|
-
console.log('[LiveReload] Disconnected, reconnecting...');
|
|
14183
|
-
if (!reconnectInterval) {
|
|
14184
|
-
reconnectInterval = setInterval(connect, 1000);
|
|
14185
|
-
}
|
|
14186
|
-
};
|
|
14187
|
-
|
|
14188
|
-
ws.onerror = function() {
|
|
14189
|
-
ws.close();
|
|
14190
|
-
};
|
|
14191
|
-
}
|
|
14192
|
-
|
|
14193
|
-
connect();
|
|
14194
|
-
})();
|
|
14195
|
-
</script>
|
|
14196
|
-
</body>
|
|
14197
|
-
</html>`;
|
|
14198
|
-
return new Response(html, {
|
|
14199
|
-
headers: {
|
|
14200
|
-
"Content-Type": "text/html",
|
|
14201
|
-
"Cache-Control": "no-cache, no-store, must-revalidate"
|
|
14202
|
-
}
|
|
14203
|
-
});
|
|
14204
|
-
}
|
|
14205
14589
|
return new Response("Not Found", { status: 404 });
|
|
14206
14590
|
},
|
|
14207
14591
|
websocket: {
|
|
@@ -14214,13 +14598,7 @@ Current dir: ${currentDir}`);
|
|
|
14214
14598
|
message(_ws, _message) {}
|
|
14215
14599
|
}
|
|
14216
14600
|
});
|
|
14217
|
-
console.log(
|
|
14218
|
-
console.log(`Open in browser: http://localhost:${port}`);
|
|
14219
|
-
if (shouldBuild) {
|
|
14220
|
-
console.log(`Live reload enabled - watching ${uiSourcePath}`);
|
|
14221
|
-
} else {
|
|
14222
|
-
console.log(`Serving pre-built UI from ${uiDistPath}`);
|
|
14223
|
-
}
|
|
14601
|
+
console.log(`\u2713 Server running at http://localhost:${port}`);
|
|
14224
14602
|
if (open) {
|
|
14225
14603
|
const openCommand = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
14226
14604
|
try {
|
|
@@ -14235,7 +14613,7 @@ async function findMarkdownFiles(dir, baseDir) {
|
|
|
14235
14613
|
const files = [];
|
|
14236
14614
|
const entries = await readdir4(dir, { withFileTypes: true });
|
|
14237
14615
|
for (const entry of entries) {
|
|
14238
|
-
const fullPath =
|
|
14616
|
+
const fullPath = join10(dir, entry.name);
|
|
14239
14617
|
if (entry.isDirectory()) {
|
|
14240
14618
|
const subFiles = await findMarkdownFiles(fullPath, baseDir);
|
|
14241
14619
|
files.push(...subFiles);
|
|
@@ -14261,7 +14639,13 @@ async function handleAPI(req, url, store, broadcast) {
|
|
|
14261
14639
|
const tasks = await store.getAllTasks();
|
|
14262
14640
|
return new Response(JSON.stringify(tasks), { headers });
|
|
14263
14641
|
}
|
|
14264
|
-
const
|
|
14642
|
+
const historyMatch = url.pathname.match(/^\/api\/tasks\/(.+)\/history$/);
|
|
14643
|
+
if (historyMatch && req.method === "GET") {
|
|
14644
|
+
const taskId = historyMatch[1];
|
|
14645
|
+
const history = await store.getTaskVersionHistory(taskId);
|
|
14646
|
+
return new Response(JSON.stringify({ versions: history }), { headers });
|
|
14647
|
+
}
|
|
14648
|
+
const taskMatch = url.pathname.match(/^\/api\/tasks\/([^/]+)$/);
|
|
14265
14649
|
if (taskMatch && req.method === "GET") {
|
|
14266
14650
|
const task = await store.getTask(taskMatch[1]);
|
|
14267
14651
|
if (!task) {
|
|
@@ -14273,10 +14657,18 @@ async function handleAPI(req, url, store, broadcast) {
|
|
|
14273
14657
|
return new Response(JSON.stringify(task), { headers });
|
|
14274
14658
|
}
|
|
14275
14659
|
if (taskMatch && req.method === "PUT") {
|
|
14276
|
-
|
|
14277
|
-
|
|
14278
|
-
|
|
14279
|
-
|
|
14660
|
+
try {
|
|
14661
|
+
const updates = await req.json();
|
|
14662
|
+
const task = await store.updateTask(taskMatch[1], updates);
|
|
14663
|
+
broadcast({ type: "tasks:updated", task });
|
|
14664
|
+
return new Response(JSON.stringify(task), { headers });
|
|
14665
|
+
} catch (error) {
|
|
14666
|
+
console.error("Error updating task:", error);
|
|
14667
|
+
return new Response(JSON.stringify({ error: String(error) }), {
|
|
14668
|
+
status: 500,
|
|
14669
|
+
headers
|
|
14670
|
+
});
|
|
14671
|
+
}
|
|
14280
14672
|
}
|
|
14281
14673
|
if (url.pathname === "/api/tasks" && req.method === "POST") {
|
|
14282
14674
|
const data = await req.json();
|
|
@@ -14287,14 +14679,45 @@ async function handleAPI(req, url, store, broadcast) {
|
|
|
14287
14679
|
status: 201
|
|
14288
14680
|
});
|
|
14289
14681
|
}
|
|
14682
|
+
if (url.pathname === "/api/notify" && req.method === "POST") {
|
|
14683
|
+
try {
|
|
14684
|
+
const { taskId, type, docPath } = await req.json();
|
|
14685
|
+
if (taskId) {
|
|
14686
|
+
const task = await store.getTask(taskId);
|
|
14687
|
+
if (task) {
|
|
14688
|
+
broadcast({ type: "tasks:updated", task });
|
|
14689
|
+
return new Response(JSON.stringify({ success: true }), { headers });
|
|
14690
|
+
}
|
|
14691
|
+
} else if (type === "tasks:refresh") {
|
|
14692
|
+
broadcast({ type: "tasks:refresh" });
|
|
14693
|
+
return new Response(JSON.stringify({ success: true }), { headers });
|
|
14694
|
+
} else if (type === "docs:updated" && docPath) {
|
|
14695
|
+
broadcast({ type: "docs:updated", docPath });
|
|
14696
|
+
return new Response(JSON.stringify({ success: true }), { headers });
|
|
14697
|
+
} else if (type === "docs:refresh") {
|
|
14698
|
+
broadcast({ type: "docs:refresh" });
|
|
14699
|
+
return new Response(JSON.stringify({ success: true }), { headers });
|
|
14700
|
+
}
|
|
14701
|
+
return new Response(JSON.stringify({ success: false, error: "Invalid notify request" }), {
|
|
14702
|
+
status: 400,
|
|
14703
|
+
headers
|
|
14704
|
+
});
|
|
14705
|
+
} catch (error) {
|
|
14706
|
+
console.error("[Server] Notify error:", error);
|
|
14707
|
+
return new Response(JSON.stringify({ error: String(error) }), {
|
|
14708
|
+
status: 500,
|
|
14709
|
+
headers
|
|
14710
|
+
});
|
|
14711
|
+
}
|
|
14712
|
+
}
|
|
14290
14713
|
if (url.pathname === "/api/docs" && req.method === "GET") {
|
|
14291
|
-
const docsDir =
|
|
14292
|
-
if (!
|
|
14714
|
+
const docsDir = join10(store.projectRoot, ".knowns", "docs");
|
|
14715
|
+
if (!existsSync7(docsDir)) {
|
|
14293
14716
|
return new Response(JSON.stringify({ docs: [] }), { headers });
|
|
14294
14717
|
}
|
|
14295
14718
|
const mdFiles = await findMarkdownFiles(docsDir, docsDir);
|
|
14296
14719
|
const docs = await Promise.all(mdFiles.map(async (relativePath) => {
|
|
14297
|
-
const fullPath =
|
|
14720
|
+
const fullPath = join10(docsDir, relativePath);
|
|
14298
14721
|
const content = await readFile3(fullPath, "utf-8");
|
|
14299
14722
|
const { data, content: docContent } = import_gray_matter3.default(content);
|
|
14300
14723
|
const pathParts = relativePath.split("/");
|
|
@@ -14310,9 +14733,88 @@ async function handleAPI(req, url, store, broadcast) {
|
|
|
14310
14733
|
}));
|
|
14311
14734
|
return new Response(JSON.stringify({ docs }), { headers });
|
|
14312
14735
|
}
|
|
14736
|
+
if (url.pathname.startsWith("/api/docs/") && req.method === "GET") {
|
|
14737
|
+
const docPath = decodeURIComponent(url.pathname.replace("/api/docs/", ""));
|
|
14738
|
+
const docsDir = join10(store.projectRoot, ".knowns", "docs");
|
|
14739
|
+
const fullPath = join10(docsDir, docPath);
|
|
14740
|
+
if (!fullPath.startsWith(docsDir)) {
|
|
14741
|
+
return new Response(JSON.stringify({ error: "Invalid path" }), {
|
|
14742
|
+
status: 400,
|
|
14743
|
+
headers
|
|
14744
|
+
});
|
|
14745
|
+
}
|
|
14746
|
+
if (!existsSync7(fullPath)) {
|
|
14747
|
+
return new Response(JSON.stringify({ error: "Document not found" }), {
|
|
14748
|
+
status: 404,
|
|
14749
|
+
headers
|
|
14750
|
+
});
|
|
14751
|
+
}
|
|
14752
|
+
const content = await readFile3(fullPath, "utf-8");
|
|
14753
|
+
const { data, content: docContent } = import_gray_matter3.default(content);
|
|
14754
|
+
const pathParts = docPath.split("/");
|
|
14755
|
+
const filename = pathParts[pathParts.length - 1];
|
|
14756
|
+
const folder = pathParts.length > 1 ? pathParts.slice(0, -1).join("/") : "";
|
|
14757
|
+
const doc = {
|
|
14758
|
+
filename,
|
|
14759
|
+
path: docPath,
|
|
14760
|
+
folder,
|
|
14761
|
+
title: data.title || filename.replace(/\.md$/, ""),
|
|
14762
|
+
description: data.description || "",
|
|
14763
|
+
tags: data.tags || [],
|
|
14764
|
+
metadata: data,
|
|
14765
|
+
content: docContent
|
|
14766
|
+
};
|
|
14767
|
+
return new Response(JSON.stringify(doc), { headers });
|
|
14768
|
+
}
|
|
14769
|
+
if (url.pathname.startsWith("/api/docs/") && req.method === "PUT") {
|
|
14770
|
+
const docPath = decodeURIComponent(url.pathname.replace("/api/docs/", ""));
|
|
14771
|
+
const docsDir = join10(store.projectRoot, ".knowns", "docs");
|
|
14772
|
+
const fullPath = join10(docsDir, docPath);
|
|
14773
|
+
if (!fullPath.startsWith(docsDir)) {
|
|
14774
|
+
return new Response(JSON.stringify({ error: "Invalid path" }), {
|
|
14775
|
+
status: 400,
|
|
14776
|
+
headers
|
|
14777
|
+
});
|
|
14778
|
+
}
|
|
14779
|
+
if (!existsSync7(fullPath)) {
|
|
14780
|
+
return new Response(JSON.stringify({ error: "Document not found" }), {
|
|
14781
|
+
status: 404,
|
|
14782
|
+
headers
|
|
14783
|
+
});
|
|
14784
|
+
}
|
|
14785
|
+
const data = await req.json();
|
|
14786
|
+
const { content, title, description, tags } = data;
|
|
14787
|
+
const existingContent = await readFile3(fullPath, "utf-8");
|
|
14788
|
+
const { data: existingData } = import_gray_matter3.default(existingContent);
|
|
14789
|
+
const now = new Date().toISOString();
|
|
14790
|
+
const updatedFrontmatter = {
|
|
14791
|
+
...existingData,
|
|
14792
|
+
title: title ?? existingData.title,
|
|
14793
|
+
description: description ?? existingData.description,
|
|
14794
|
+
tags: tags ?? existingData.tags,
|
|
14795
|
+
updatedAt: now
|
|
14796
|
+
};
|
|
14797
|
+
const newFileContent = import_gray_matter3.default.stringify(content ?? "", updatedFrontmatter);
|
|
14798
|
+
await writeFile2(fullPath, newFileContent, "utf-8");
|
|
14799
|
+
const pathParts = docPath.split("/");
|
|
14800
|
+
const filename = pathParts[pathParts.length - 1];
|
|
14801
|
+
const folder = pathParts.length > 1 ? pathParts.slice(0, -1).join("/") : "";
|
|
14802
|
+
const updatedDoc = {
|
|
14803
|
+
filename,
|
|
14804
|
+
path: docPath,
|
|
14805
|
+
folder,
|
|
14806
|
+
title: updatedFrontmatter.title || filename.replace(/\.md$/, ""),
|
|
14807
|
+
description: updatedFrontmatter.description || "",
|
|
14808
|
+
tags: updatedFrontmatter.tags || [],
|
|
14809
|
+
metadata: updatedFrontmatter,
|
|
14810
|
+
content: content ?? ""
|
|
14811
|
+
};
|
|
14812
|
+
broadcast({ type: "docs:updated", doc: updatedDoc });
|
|
14813
|
+
return new Response(JSON.stringify(updatedDoc), { headers });
|
|
14814
|
+
}
|
|
14313
14815
|
if (url.pathname === "/api/docs" && req.method === "POST") {
|
|
14314
|
-
const docsDir =
|
|
14315
|
-
if (!
|
|
14816
|
+
const docsDir = join10(store.projectRoot, ".knowns", "docs");
|
|
14817
|
+
if (!existsSync7(docsDir)) {
|
|
14316
14818
|
await mkdir5(docsDir, { recursive: true });
|
|
14317
14819
|
}
|
|
14318
14820
|
const data = await req.json();
|
|
@@ -14328,16 +14830,16 @@ async function handleAPI(req, url, store, broadcast) {
|
|
|
14328
14830
|
let targetDir;
|
|
14329
14831
|
if (folder?.trim()) {
|
|
14330
14832
|
const cleanFolder = folder.trim().replace(/^\/+|\/+$/g, "");
|
|
14331
|
-
targetDir =
|
|
14332
|
-
filepath =
|
|
14833
|
+
targetDir = join10(docsDir, cleanFolder);
|
|
14834
|
+
filepath = join10(targetDir, filename);
|
|
14333
14835
|
} else {
|
|
14334
14836
|
targetDir = docsDir;
|
|
14335
|
-
filepath =
|
|
14837
|
+
filepath = join10(docsDir, filename);
|
|
14336
14838
|
}
|
|
14337
|
-
if (!
|
|
14839
|
+
if (!existsSync7(targetDir)) {
|
|
14338
14840
|
await mkdir5(targetDir, { recursive: true });
|
|
14339
14841
|
}
|
|
14340
|
-
if (
|
|
14842
|
+
if (existsSync7(filepath)) {
|
|
14341
14843
|
return new Response(JSON.stringify({ error: "Document with this title already exists in this folder" }), {
|
|
14342
14844
|
status: 409,
|
|
14343
14845
|
headers
|
|
@@ -14362,8 +14864,8 @@ async function handleAPI(req, url, store, broadcast) {
|
|
|
14362
14864
|
}), { status: 201, headers });
|
|
14363
14865
|
}
|
|
14364
14866
|
if (url.pathname === "/api/config" && req.method === "GET") {
|
|
14365
|
-
const configPath =
|
|
14366
|
-
if (!
|
|
14867
|
+
const configPath = join10(store.projectRoot, ".knowns", "config.json");
|
|
14868
|
+
if (!existsSync7(configPath)) {
|
|
14367
14869
|
return new Response(JSON.stringify({
|
|
14368
14870
|
config: {
|
|
14369
14871
|
name: "Knowns",
|
|
@@ -14390,9 +14892,9 @@ async function handleAPI(req, url, store, broadcast) {
|
|
|
14390
14892
|
}
|
|
14391
14893
|
if (url.pathname === "/api/config" && req.method === "POST") {
|
|
14392
14894
|
const config = await req.json();
|
|
14393
|
-
const configPath =
|
|
14895
|
+
const configPath = join10(store.projectRoot, ".knowns", "config.json");
|
|
14394
14896
|
let existingData = {};
|
|
14395
|
-
if (
|
|
14897
|
+
if (existsSync7(configPath)) {
|
|
14396
14898
|
const content = await readFile3(configPath, "utf-8");
|
|
14397
14899
|
existingData = JSON.parse(content);
|
|
14398
14900
|
}
|
|
@@ -14405,6 +14907,33 @@ async function handleAPI(req, url, store, broadcast) {
|
|
|
14405
14907
|
await writeFile2(configPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
14406
14908
|
return new Response(JSON.stringify({ success: true }), { headers });
|
|
14407
14909
|
}
|
|
14910
|
+
if (url.pathname === "/api/activities" && req.method === "GET") {
|
|
14911
|
+
const limit = Number.parseInt(url.searchParams.get("limit") || "50", 10);
|
|
14912
|
+
const type = url.searchParams.get("type");
|
|
14913
|
+
const tasks = await store.getAllTasks();
|
|
14914
|
+
const allActivities = [];
|
|
14915
|
+
for (const task of tasks) {
|
|
14916
|
+
const versions = await store.getTaskVersionHistory(task.id);
|
|
14917
|
+
for (const version of versions) {
|
|
14918
|
+
if (type) {
|
|
14919
|
+
const hasMatchingChange = version.changes.some((c) => c.field === type);
|
|
14920
|
+
if (!hasMatchingChange)
|
|
14921
|
+
continue;
|
|
14922
|
+
}
|
|
14923
|
+
allActivities.push({
|
|
14924
|
+
taskId: task.id,
|
|
14925
|
+
taskTitle: task.title,
|
|
14926
|
+
version: version.version,
|
|
14927
|
+
timestamp: version.timestamp,
|
|
14928
|
+
author: version.author,
|
|
14929
|
+
changes: version.changes
|
|
14930
|
+
});
|
|
14931
|
+
}
|
|
14932
|
+
}
|
|
14933
|
+
allActivities.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
14934
|
+
const limited = allActivities.slice(0, limit);
|
|
14935
|
+
return new Response(JSON.stringify({ activities: limited }), { headers });
|
|
14936
|
+
}
|
|
14408
14937
|
if (url.pathname === "/api/search" && req.method === "GET") {
|
|
14409
14938
|
const query = url.searchParams.get("q");
|
|
14410
14939
|
if (!query) {
|
|
@@ -14424,12 +14953,12 @@ async function handleAPI(req, url, store, broadcast) {
|
|
|
14424
14953
|
].join(" ").toLowerCase();
|
|
14425
14954
|
return searchText.includes(q);
|
|
14426
14955
|
});
|
|
14427
|
-
const docsDir =
|
|
14956
|
+
const docsDir = join10(store.projectRoot, ".knowns", "docs");
|
|
14428
14957
|
const docResults = [];
|
|
14429
|
-
if (
|
|
14958
|
+
if (existsSync7(docsDir)) {
|
|
14430
14959
|
const mdFiles = await findMarkdownFiles(docsDir, docsDir);
|
|
14431
14960
|
for (const relativePath of mdFiles) {
|
|
14432
|
-
const fullPath =
|
|
14961
|
+
const fullPath = join10(docsDir, relativePath);
|
|
14433
14962
|
const content = await readFile3(fullPath, "utf-8");
|
|
14434
14963
|
const { data, content: docContent } = import_gray_matter3.default(content);
|
|
14435
14964
|
const searchText = `${data.title || ""} ${data.description || ""} ${data.tags?.join(" ") || ""} ${docContent}`.toLowerCase();
|
|
@@ -14464,18 +14993,42 @@ async function handleAPI(req, url, store, broadcast) {
|
|
|
14464
14993
|
}
|
|
14465
14994
|
|
|
14466
14995
|
// src/commands/browser.ts
|
|
14467
|
-
var
|
|
14996
|
+
var DEFAULT_PORT2 = 6420;
|
|
14997
|
+
var CONFIG_FILE = ".knowns/config.json";
|
|
14998
|
+
async function saveServerPort(projectRoot, port) {
|
|
14999
|
+
const configPath = join11(projectRoot, CONFIG_FILE);
|
|
15000
|
+
const knownsDir = join11(projectRoot, ".knowns");
|
|
15001
|
+
if (!existsSync8(knownsDir)) {
|
|
15002
|
+
await mkdir6(knownsDir, { recursive: true });
|
|
15003
|
+
}
|
|
15004
|
+
try {
|
|
15005
|
+
let config = {};
|
|
15006
|
+
if (existsSync8(configPath)) {
|
|
15007
|
+
const content = await readFile4(configPath, "utf-8");
|
|
15008
|
+
config = JSON.parse(content);
|
|
15009
|
+
}
|
|
15010
|
+
const settings = config.settings || {};
|
|
15011
|
+
settings.serverPort = port;
|
|
15012
|
+
config.settings = settings;
|
|
15013
|
+
await writeFile3(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
15014
|
+
} catch {}
|
|
15015
|
+
}
|
|
15016
|
+
var browserCommand = new Command("browser").description("Open web UI for task management").option("-p, --port <port>", "Port number", String(DEFAULT_PORT2)).option("--no-open", "Don't open browser automatically").action(async (options2) => {
|
|
14468
15017
|
const projectRoot = findProjectRoot();
|
|
14469
15018
|
if (!projectRoot) {
|
|
14470
15019
|
console.error(source_default.red("\u2717 Not in a Knowns project"));
|
|
14471
15020
|
console.log(source_default.gray("Run 'knowns init' to initialize a project"));
|
|
14472
15021
|
process.exit(1);
|
|
14473
15022
|
}
|
|
15023
|
+
const port = Number.parseInt(options2.port);
|
|
15024
|
+
if (options2.port !== String(DEFAULT_PORT2)) {
|
|
15025
|
+
await saveServerPort(projectRoot, port);
|
|
15026
|
+
}
|
|
14474
15027
|
console.log(source_default.cyan("\u25C6 Starting Knowns.dev Web UI..."));
|
|
14475
15028
|
console.log("");
|
|
14476
15029
|
try {
|
|
14477
15030
|
await startServer({
|
|
14478
|
-
port
|
|
15031
|
+
port,
|
|
14479
15032
|
projectRoot,
|
|
14480
15033
|
open: options2.open
|
|
14481
15034
|
});
|
|
@@ -14487,17 +15040,17 @@ var browserCommand = new Command("browser").description("Open web UI for task ma
|
|
|
14487
15040
|
}
|
|
14488
15041
|
});
|
|
14489
15042
|
// src/commands/doc.ts
|
|
14490
|
-
import { existsSync as
|
|
14491
|
-
import { mkdir as
|
|
14492
|
-
import { join as
|
|
15043
|
+
import { existsSync as existsSync9 } from "fs";
|
|
15044
|
+
import { mkdir as mkdir7, readFile as readFile5, readdir as readdir5, writeFile as writeFile4 } from "fs/promises";
|
|
15045
|
+
import { join as join12 } from "path";
|
|
14493
15046
|
var import_gray_matter4 = __toESM(require_gray_matter(), 1);
|
|
14494
|
-
var DOCS_DIR =
|
|
15047
|
+
var DOCS_DIR = join12(process.cwd(), ".knowns", "docs");
|
|
14495
15048
|
async function getAllMdFiles(dir, basePath = "") {
|
|
14496
15049
|
const files = [];
|
|
14497
15050
|
const entries = await readdir5(dir, { withFileTypes: true });
|
|
14498
15051
|
for (const entry of entries) {
|
|
14499
|
-
const fullPath =
|
|
14500
|
-
const relativePath = basePath ?
|
|
15052
|
+
const fullPath = join12(dir, entry.name);
|
|
15053
|
+
const relativePath = basePath ? join12(basePath, entry.name) : entry.name;
|
|
14501
15054
|
if (entry.isDirectory()) {
|
|
14502
15055
|
const subFiles = await getAllMdFiles(fullPath, relativePath);
|
|
14503
15056
|
files.push(...subFiles);
|
|
@@ -14508,20 +15061,30 @@ async function getAllMdFiles(dir, basePath = "") {
|
|
|
14508
15061
|
return files;
|
|
14509
15062
|
}
|
|
14510
15063
|
async function ensureDocsDir() {
|
|
14511
|
-
if (!
|
|
14512
|
-
await
|
|
15064
|
+
if (!existsSync9(DOCS_DIR)) {
|
|
15065
|
+
await mkdir7(DOCS_DIR, { recursive: true });
|
|
14513
15066
|
}
|
|
14514
15067
|
}
|
|
14515
15068
|
function titleToFilename(title) {
|
|
14516
15069
|
return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
14517
15070
|
}
|
|
14518
|
-
var createCommand3 = new Command("create").description("Create a new documentation file").argument("<title>", "Document title").option("-d, --description <text>", "Document description").option("-t, --tags <tags>", "Comma-separated tags").option("--plain", "Plain text output for AI").action(async (title, options2) => {
|
|
15071
|
+
var createCommand3 = new Command("create").description("Create a new documentation file").argument("<title>", "Document title").option("-d, --description <text>", "Document description").option("-t, --tags <tags>", "Comma-separated tags").option("-f, --folder <path>", "Folder path (e.g., guides, patterns/auth)").option("--plain", "Plain text output for AI").action(async (title, options2) => {
|
|
14519
15072
|
try {
|
|
14520
15073
|
await ensureDocsDir();
|
|
14521
15074
|
const filename = `${titleToFilename(title)}.md`;
|
|
14522
|
-
|
|
14523
|
-
|
|
14524
|
-
|
|
15075
|
+
let targetDir = DOCS_DIR;
|
|
15076
|
+
let relativePath = filename;
|
|
15077
|
+
if (options2.folder) {
|
|
15078
|
+
const folderPath = options2.folder.replace(/^\/|\/$/g, "");
|
|
15079
|
+
targetDir = join12(DOCS_DIR, folderPath);
|
|
15080
|
+
relativePath = join12(folderPath, filename);
|
|
15081
|
+
if (!existsSync9(targetDir)) {
|
|
15082
|
+
await mkdir7(targetDir, { recursive: true });
|
|
15083
|
+
}
|
|
15084
|
+
}
|
|
15085
|
+
const filepath = join12(targetDir, filename);
|
|
15086
|
+
if (existsSync9(filepath)) {
|
|
15087
|
+
console.error(source_default.red(`\u2717 Document already exists: ${relativePath}`));
|
|
14525
15088
|
process.exit(1);
|
|
14526
15089
|
}
|
|
14527
15090
|
const now = new Date().toISOString();
|
|
@@ -14540,11 +15103,12 @@ var createCommand3 = new Command("create").description("Create a new documentati
|
|
|
14540
15103
|
|
|
14541
15104
|
Write your documentation here.
|
|
14542
15105
|
`, metadata);
|
|
14543
|
-
await
|
|
15106
|
+
await writeFile4(filepath, content, "utf-8");
|
|
15107
|
+
await notifyDocUpdate(relativePath);
|
|
14544
15108
|
if (options2.plain) {
|
|
14545
|
-
console.log(`Created: ${
|
|
15109
|
+
console.log(`Created: ${relativePath}`);
|
|
14546
15110
|
} else {
|
|
14547
|
-
console.log(source_default.green(`\u2713 Created documentation: ${source_default.bold(
|
|
15111
|
+
console.log(source_default.green(`\u2713 Created documentation: ${source_default.bold(relativePath)}`));
|
|
14548
15112
|
console.log(source_default.gray(` Location: ${filepath}`));
|
|
14549
15113
|
}
|
|
14550
15114
|
} catch (error) {
|
|
@@ -14567,7 +15131,7 @@ var listCommand2 = new Command("list").description("List all documentation files
|
|
|
14567
15131
|
}
|
|
14568
15132
|
const docs = [];
|
|
14569
15133
|
for (const file of mdFiles) {
|
|
14570
|
-
const content = await
|
|
15134
|
+
const content = await readFile5(join12(DOCS_DIR, file), "utf-8");
|
|
14571
15135
|
const { data } = import_gray_matter4.default(content);
|
|
14572
15136
|
docs.push({
|
|
14573
15137
|
filename: file,
|
|
@@ -14626,12 +15190,12 @@ var viewCommand2 = new Command("view").description("View a documentation file").
|
|
|
14626
15190
|
try {
|
|
14627
15191
|
await ensureDocsDir();
|
|
14628
15192
|
let filename = name.endsWith(".md") ? name : `${name}.md`;
|
|
14629
|
-
let filepath =
|
|
14630
|
-
if (!
|
|
15193
|
+
let filepath = join12(DOCS_DIR, filename);
|
|
15194
|
+
if (!existsSync9(filepath)) {
|
|
14631
15195
|
filename = `${titleToFilename(name)}.md`;
|
|
14632
|
-
filepath =
|
|
15196
|
+
filepath = join12(DOCS_DIR, filename);
|
|
14633
15197
|
}
|
|
14634
|
-
if (!
|
|
15198
|
+
if (!existsSync9(filepath)) {
|
|
14635
15199
|
const allFiles = await getAllMdFiles(DOCS_DIR);
|
|
14636
15200
|
const searchName = name.toLowerCase().replace(/\.md$/, "");
|
|
14637
15201
|
const matchingFile = allFiles.find((file) => {
|
|
@@ -14641,14 +15205,14 @@ var viewCommand2 = new Command("view").description("View a documentation file").
|
|
|
14641
15205
|
});
|
|
14642
15206
|
if (matchingFile) {
|
|
14643
15207
|
filename = matchingFile;
|
|
14644
|
-
filepath =
|
|
15208
|
+
filepath = join12(DOCS_DIR, matchingFile);
|
|
14645
15209
|
}
|
|
14646
15210
|
}
|
|
14647
|
-
if (!
|
|
15211
|
+
if (!existsSync9(filepath)) {
|
|
14648
15212
|
console.error(source_default.red(`\u2717 Documentation not found: ${name}`));
|
|
14649
15213
|
process.exit(1);
|
|
14650
15214
|
}
|
|
14651
|
-
const fileContent = await
|
|
15215
|
+
const fileContent = await readFile5(filepath, "utf-8");
|
|
14652
15216
|
const { data, content } = import_gray_matter4.default(fileContent);
|
|
14653
15217
|
const metadata = data;
|
|
14654
15218
|
if (options2.plain) {
|
|
@@ -14701,6 +15265,13 @@ var viewCommand2 = new Command("view").description("View a documentation file").
|
|
|
14701
15265
|
for (const { original, resolved } of linksToAdd) {
|
|
14702
15266
|
enhancedContent = enhancedContent.replace(original, resolved);
|
|
14703
15267
|
}
|
|
15268
|
+
const knownProjectRoot = findProjectRoot();
|
|
15269
|
+
if (knownProjectRoot) {
|
|
15270
|
+
const fileStore = new FileStore(knownProjectRoot);
|
|
15271
|
+
const allTasks = await fileStore.getAllTasks();
|
|
15272
|
+
const taskMap = buildTaskMap(allTasks);
|
|
15273
|
+
enhancedContent = transformMentionsToRefs(enhancedContent, taskMap);
|
|
15274
|
+
}
|
|
14704
15275
|
console.log(enhancedContent);
|
|
14705
15276
|
} else {
|
|
14706
15277
|
console.log(source_default.bold(`
|
|
@@ -14723,20 +15294,34 @@ var viewCommand2 = new Command("view").description("View a documentation file").
|
|
|
14723
15294
|
process.exit(1);
|
|
14724
15295
|
}
|
|
14725
15296
|
});
|
|
14726
|
-
var editCommand2 = new Command("edit").description("Edit a documentation file metadata").argument("<name>", "Document name").option("-t, --title <text>", "New title").option("-d, --description <text>", "New description").option("--tags <tags>", "Comma-separated tags").action(async (name, options2) => {
|
|
15297
|
+
var editCommand2 = new Command("edit").description("Edit a documentation file (metadata and content)").argument("<name>", "Document name or path (e.g., guides/my-doc)").option("-t, --title <text>", "New title").option("-d, --description <text>", "New description").option("--tags <tags>", "Comma-separated tags").option("-c, --content <text>", "Replace content").option("-a, --append <text>", "Append to content").option("--plain", "Plain text output for AI").action(async (name, options2) => {
|
|
14727
15298
|
try {
|
|
14728
15299
|
await ensureDocsDir();
|
|
14729
15300
|
let filename = name.endsWith(".md") ? name : `${name}.md`;
|
|
14730
|
-
let filepath =
|
|
14731
|
-
if (!
|
|
14732
|
-
|
|
14733
|
-
|
|
15301
|
+
let filepath = join12(DOCS_DIR, filename);
|
|
15302
|
+
if (!existsSync9(filepath)) {
|
|
15303
|
+
const baseName = name.includes("/") ? name : titleToFilename(name);
|
|
15304
|
+
filename = baseName.endsWith(".md") ? baseName : `${baseName}.md`;
|
|
15305
|
+
filepath = join12(DOCS_DIR, filename);
|
|
14734
15306
|
}
|
|
14735
|
-
if (!
|
|
15307
|
+
if (!existsSync9(filepath)) {
|
|
15308
|
+
const allFiles = await getAllMdFiles(DOCS_DIR);
|
|
15309
|
+
const searchName = name.toLowerCase().replace(/\.md$/, "");
|
|
15310
|
+
const matchingFile = allFiles.find((file) => {
|
|
15311
|
+
const fileNameOnly = file.toLowerCase().replace(/\.md$/, "");
|
|
15312
|
+
const fileBaseName = file.split("/").pop()?.toLowerCase().replace(/\.md$/, "");
|
|
15313
|
+
return fileNameOnly === searchName || fileBaseName === searchName || file === name;
|
|
15314
|
+
});
|
|
15315
|
+
if (matchingFile) {
|
|
15316
|
+
filename = matchingFile;
|
|
15317
|
+
filepath = join12(DOCS_DIR, matchingFile);
|
|
15318
|
+
}
|
|
15319
|
+
}
|
|
15320
|
+
if (!existsSync9(filepath)) {
|
|
14736
15321
|
console.error(source_default.red(`\u2717 Documentation not found: ${name}`));
|
|
14737
15322
|
process.exit(1);
|
|
14738
15323
|
}
|
|
14739
|
-
const fileContent = await
|
|
15324
|
+
const fileContent = await readFile5(filepath, "utf-8");
|
|
14740
15325
|
const { data, content } = import_gray_matter4.default(fileContent);
|
|
14741
15326
|
const metadata = data;
|
|
14742
15327
|
if (options2.title)
|
|
@@ -14746,9 +15331,23 @@ var editCommand2 = new Command("edit").description("Edit a documentation file me
|
|
|
14746
15331
|
if (options2.tags)
|
|
14747
15332
|
metadata.tags = options2.tags.split(",").map((t) => t.trim());
|
|
14748
15333
|
metadata.updatedAt = new Date().toISOString();
|
|
14749
|
-
|
|
14750
|
-
|
|
14751
|
-
|
|
15334
|
+
let updatedContent = content;
|
|
15335
|
+
if (options2.content) {
|
|
15336
|
+
updatedContent = options2.content;
|
|
15337
|
+
}
|
|
15338
|
+
if (options2.append) {
|
|
15339
|
+
updatedContent = `${content.trimEnd()}
|
|
15340
|
+
|
|
15341
|
+
${options2.append}`;
|
|
15342
|
+
}
|
|
15343
|
+
const newFileContent = import_gray_matter4.default.stringify(updatedContent, metadata);
|
|
15344
|
+
await writeFile4(filepath, newFileContent, "utf-8");
|
|
15345
|
+
await notifyDocUpdate(filename);
|
|
15346
|
+
if (options2.plain) {
|
|
15347
|
+
console.log(`Updated: ${filename}`);
|
|
15348
|
+
} else {
|
|
15349
|
+
console.log(source_default.green(`\u2713 Updated documentation: ${source_default.bold(filename)}`));
|
|
15350
|
+
}
|
|
14752
15351
|
} catch (error) {
|
|
14753
15352
|
console.error(source_default.red("Error editing documentation:"), error instanceof Error ? error.message : String(error));
|
|
14754
15353
|
process.exit(1);
|
|
@@ -14756,9 +15355,9 @@ var editCommand2 = new Command("edit").description("Edit a documentation file me
|
|
|
14756
15355
|
});
|
|
14757
15356
|
var docCommand = new Command("doc").description("Manage documentation").addCommand(createCommand3).addCommand(listCommand2).addCommand(viewCommand2).addCommand(editCommand2);
|
|
14758
15357
|
// src/commands/config.ts
|
|
14759
|
-
import { existsSync as
|
|
14760
|
-
import { mkdir as
|
|
14761
|
-
import { join as
|
|
15358
|
+
import { existsSync as existsSync10 } from "fs";
|
|
15359
|
+
import { mkdir as mkdir8, readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
|
|
15360
|
+
import { join as join13 } from "path";
|
|
14762
15361
|
|
|
14763
15362
|
// node_modules/zod/v4/classic/external.js
|
|
14764
15363
|
var exports_external = {};
|
|
@@ -28173,20 +28772,22 @@ function date4(params) {
|
|
|
28173
28772
|
// node_modules/zod/v4/classic/external.js
|
|
28174
28773
|
config(en_default());
|
|
28175
28774
|
// src/commands/config.ts
|
|
28176
|
-
var
|
|
28775
|
+
var CONFIG_FILE2 = ".knowns/config.json";
|
|
28177
28776
|
var ConfigSchema = exports_external.object({
|
|
28178
28777
|
defaultAssignee: exports_external.string().optional(),
|
|
28179
28778
|
defaultPriority: exports_external.enum(["low", "medium", "high"]).optional(),
|
|
28180
28779
|
defaultLabels: exports_external.array(exports_external.string()).optional(),
|
|
28181
28780
|
timeFormat: exports_external.enum(["12h", "24h"]).optional(),
|
|
28182
28781
|
editor: exports_external.string().optional(),
|
|
28183
|
-
visibleColumns: exports_external.array(exports_external.enum(["todo", "in-progress", "in-review", "done", "blocked"])).optional()
|
|
28782
|
+
visibleColumns: exports_external.array(exports_external.enum(["todo", "in-progress", "in-review", "done", "blocked"])).optional(),
|
|
28783
|
+
serverPort: exports_external.number().optional()
|
|
28184
28784
|
});
|
|
28185
28785
|
var DEFAULT_CONFIG = {
|
|
28186
28786
|
defaultPriority: "medium",
|
|
28187
28787
|
defaultLabels: [],
|
|
28188
28788
|
timeFormat: "24h",
|
|
28189
|
-
visibleColumns: ["todo", "in-progress", "done"]
|
|
28789
|
+
visibleColumns: ["todo", "in-progress", "done"],
|
|
28790
|
+
serverPort: 6420
|
|
28190
28791
|
};
|
|
28191
28792
|
function getProjectRoot2() {
|
|
28192
28793
|
const projectRoot = findProjectRoot();
|
|
@@ -28198,12 +28799,12 @@ function getProjectRoot2() {
|
|
|
28198
28799
|
return projectRoot;
|
|
28199
28800
|
}
|
|
28200
28801
|
async function loadConfig(projectRoot) {
|
|
28201
|
-
const configPath =
|
|
28202
|
-
if (!
|
|
28802
|
+
const configPath = join13(projectRoot, CONFIG_FILE2);
|
|
28803
|
+
if (!existsSync10(configPath)) {
|
|
28203
28804
|
return { ...DEFAULT_CONFIG };
|
|
28204
28805
|
}
|
|
28205
28806
|
try {
|
|
28206
|
-
const content = await
|
|
28807
|
+
const content = await readFile6(configPath, "utf-8");
|
|
28207
28808
|
const data = JSON.parse(content);
|
|
28208
28809
|
const settings = data.settings || {};
|
|
28209
28810
|
const validated = ConfigSchema.parse(settings);
|
|
@@ -28217,22 +28818,22 @@ async function loadConfig(projectRoot) {
|
|
|
28217
28818
|
}
|
|
28218
28819
|
}
|
|
28219
28820
|
async function saveConfig(projectRoot, config2) {
|
|
28220
|
-
const configPath =
|
|
28221
|
-
const knownsDir =
|
|
28222
|
-
if (!
|
|
28223
|
-
await
|
|
28821
|
+
const configPath = join13(projectRoot, CONFIG_FILE2);
|
|
28822
|
+
const knownsDir = join13(projectRoot, ".knowns");
|
|
28823
|
+
if (!existsSync10(knownsDir)) {
|
|
28824
|
+
await mkdir8(knownsDir, { recursive: true });
|
|
28224
28825
|
}
|
|
28225
28826
|
try {
|
|
28226
28827
|
let existingData = {};
|
|
28227
|
-
if (
|
|
28228
|
-
const content = await
|
|
28828
|
+
if (existsSync10(configPath)) {
|
|
28829
|
+
const content = await readFile6(configPath, "utf-8");
|
|
28229
28830
|
existingData = JSON.parse(content);
|
|
28230
28831
|
}
|
|
28231
28832
|
const merged = {
|
|
28232
28833
|
...existingData,
|
|
28233
28834
|
settings: config2
|
|
28234
28835
|
};
|
|
28235
|
-
await
|
|
28836
|
+
await writeFile5(configPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
28236
28837
|
} catch (error46) {
|
|
28237
28838
|
console.error(source_default.red("\u2717 Failed to save config"));
|
|
28238
28839
|
if (error46 instanceof Error) {
|
|
@@ -28374,9 +28975,94 @@ var resetCommand = new Command("reset").description("Reset configuration to defa
|
|
|
28374
28975
|
}
|
|
28375
28976
|
});
|
|
28376
28977
|
var configCommand = new Command("config").description("Manage configuration settings").addCommand(listCommand3).addCommand(getCommand).addCommand(setCommand).addCommand(resetCommand);
|
|
28978
|
+
// package.json
|
|
28979
|
+
var package_default = {
|
|
28980
|
+
name: "knowns",
|
|
28981
|
+
version: "0.1.6",
|
|
28982
|
+
description: "CLI tool for dev teams to manage tasks and documentation",
|
|
28983
|
+
module: "index.ts",
|
|
28984
|
+
type: "module",
|
|
28985
|
+
bin: {
|
|
28986
|
+
knowns: "./dist/index.js",
|
|
28987
|
+
kn: "./dist/index.js"
|
|
28988
|
+
},
|
|
28989
|
+
main: "./dist/index.js",
|
|
28990
|
+
files: ["dist", "CLAUDE.md", "README.md", "CHANGELOG.md", "LICENSE"],
|
|
28991
|
+
keywords: ["cli", "task-management", "documentation", "knowledge-base", "team-tools", "developer-tools"],
|
|
28992
|
+
author: "howznguyen <howznguyen@knowns.dev>",
|
|
28993
|
+
license: "MIT",
|
|
28994
|
+
repository: {
|
|
28995
|
+
type: "git",
|
|
28996
|
+
url: "https://github.com/knowns-dev/knowns.git"
|
|
28997
|
+
},
|
|
28998
|
+
homepage: "https://knowns.dev",
|
|
28999
|
+
bugs: {
|
|
29000
|
+
url: "https://github.com/knowns-dev/knowns/issues"
|
|
29001
|
+
},
|
|
29002
|
+
scripts: {
|
|
29003
|
+
dev: "bun run --watch src/index.ts browser --no-open",
|
|
29004
|
+
watch: "nodemon",
|
|
29005
|
+
build: "bun run build:cli && bun run build:ui",
|
|
29006
|
+
"build:cli": "bun build src/index.ts --outdir dist --target bun && bun build src/mcp/server.ts --outdir dist/mcp --target bun",
|
|
29007
|
+
"build:ui": "vite build",
|
|
29008
|
+
start: "bun run src/index.ts",
|
|
29009
|
+
mcp: "bun run src/mcp/server.ts",
|
|
29010
|
+
test: "bun test",
|
|
29011
|
+
lint: "biome check .",
|
|
29012
|
+
"lint:fix": "biome check --write .",
|
|
29013
|
+
format: "biome format --write .",
|
|
29014
|
+
prepare: "husky"
|
|
29015
|
+
},
|
|
29016
|
+
dependencies: {
|
|
29017
|
+
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
29018
|
+
"@radix-ui/react-collapsible": "^1.1.12",
|
|
29019
|
+
"@radix-ui/react-dialog": "^1.1.15",
|
|
29020
|
+
"@radix-ui/react-navigation-menu": "^1.2.14",
|
|
29021
|
+
"@radix-ui/react-popover": "^1.1.15",
|
|
29022
|
+
"@radix-ui/react-scroll-area": "^1.2.10",
|
|
29023
|
+
"@radix-ui/react-separator": "^1.1.8",
|
|
29024
|
+
"@radix-ui/react-slot": "^1.2.4",
|
|
29025
|
+
"@radix-ui/react-tooltip": "^1.2.8",
|
|
29026
|
+
"@uiw/react-md-editor": "^4.0.11",
|
|
29027
|
+
chalk: "^5.3.0",
|
|
29028
|
+
"class-variance-authority": "^0.7.1",
|
|
29029
|
+
clsx: "^2.1.1",
|
|
29030
|
+
cmdk: "^1.1.1",
|
|
29031
|
+
commander: "^12.1.0",
|
|
29032
|
+
"gray-matter": "^4.0.3",
|
|
29033
|
+
"lucide-react": "^0.562.0",
|
|
29034
|
+
marked: "^17.0.1",
|
|
29035
|
+
prompts: "^2.4.2",
|
|
29036
|
+
react: "^19.2.3",
|
|
29037
|
+
"react-diff-viewer": "^3.1.1",
|
|
29038
|
+
"react-dom": "^19.2.3",
|
|
29039
|
+
"tailwind-merge": "^3.4.0",
|
|
29040
|
+
turndown: "^7.2.2",
|
|
29041
|
+
"unist-util-visit": "^5.0.0",
|
|
29042
|
+
zod: "^4.2.1"
|
|
29043
|
+
},
|
|
29044
|
+
devDependencies: {
|
|
29045
|
+
"@biomejs/biome": "^1.9.4",
|
|
29046
|
+
"@tailwindcss/vite": "^4.1.18",
|
|
29047
|
+
"@types/bun": "latest",
|
|
29048
|
+
"@types/prompts": "^2.4.9",
|
|
29049
|
+
"@types/react": "^19.2.7",
|
|
29050
|
+
"@types/react-dom": "^19.2.3",
|
|
29051
|
+
"@vitejs/plugin-react": "^5.1.2",
|
|
29052
|
+
autoprefixer: "^10.4.23",
|
|
29053
|
+
concurrently: "^9.2.1",
|
|
29054
|
+
husky: "^9.1.7",
|
|
29055
|
+
"lint-staged": "^15.2.10",
|
|
29056
|
+
nodemon: "^3.1.11",
|
|
29057
|
+
tailwindcss: "^4.1.18",
|
|
29058
|
+
typescript: "^5.7.2",
|
|
29059
|
+
vite: "^7.3.0"
|
|
29060
|
+
}
|
|
29061
|
+
};
|
|
29062
|
+
|
|
28377
29063
|
// src/index.ts
|
|
28378
29064
|
var program2 = new Command;
|
|
28379
|
-
program2.name("knowns").description("CLI tool for dev teams to manage tasks, track time, and sync").version(
|
|
29065
|
+
program2.name("knowns").description("CLI tool for dev teams to manage tasks, track time, and sync").version(package_default.version);
|
|
28380
29066
|
program2.addCommand(initCommand);
|
|
28381
29067
|
program2.addCommand(taskCommand);
|
|
28382
29068
|
program2.addCommand(boardCommand);
|